using UnityEngine;
using System.Collections;
using System.Collections.Generic;


namespace Lch
{
    public partial class MeshLodHelper
    {


        public delegate bool IsAlwayKeep(int index);
        public static IsAlwayKeep isAlwayKeep = null;
        private enum MeshTopology
        {
            Mixed, // Tris & Quads
            Triangles,
            Quads
        }

        private class BaseMesh
        {
            public List<Vertex> vertices = new List<Vertex>();
            public List<Face> faces = new List<Face>();
            public int numValidVerts = 0;
            public int numValidFaces = 0;
            public int numMaterials = 1;
            public bool hasVertexColors = false;
            public bool hasBoneWeights = false;
            public MeshTopology topology = MeshTopology.Mixed;
            public bool hasUV1 = false;
            public bool hasUV2 = false;
            public float equalityTolerance = 0.0f;

            public Matrix4x4[] bindposes = null;
            public bool calculateTangents = true;
            protected int uniqueTag = 1 << 24;

            public int GetUniqueTag()
            {
                return uniqueTag++;
            }



            public int vertCount() { return vertices.Count; }

            public Vertex corner(int faceIndex, int cornerIndex)
            {
                return vertices[faces[faceIndex].v[cornerIndex]];
            }

            public int AddVertex(Vector3 coords)
            {
                int vid = vertices.Count;
                vertices.Add(new Vertex(coords));
                numValidVerts++;
                return vid;
            }

            public bool IsVertexValid(int vertexIndex)
            {
                return vertices[vertexIndex].valid;
            }

            public int CompareVertices(int vertexIndexA, int vertexIndexB, float positionTolerance)
            {
                Vertex va = vertices[vertexIndexA];
                Vertex vb = vertices[vertexIndexB];

                int res = Vector3CompareWithTolerance(va.coords, vb.coords, positionTolerance);
                if (res != 0) return res;
                if (hasBoneWeights)
                {
                    res = BoneWeightCompare(va.boneWeight, vb.boneWeight);
                    if (res != 0) return res;
                }
                return 0;
            }

            public void ReplaceVertex(int vertexIndexOld, int vertexIndexNew)
            {
                IndexList linkedFaces = vertices[vertexIndexOld].linkedFaces;
                int num = linkedFaces.Count;
                for (int i = 0; i < num; ++i)
                {
                    int faceIndex = linkedFaces[i];
                    int n2 = faces[faceIndex].ReplaceVertex(vertexIndexOld, vertexIndexNew);
                    if (n2 != 1)
                    {
                        Debug.LogError("Weird error vertex found " + n2 + " times in face.");
                    }
                    vertices[vertexIndexNew].linkedFaces.Add(faceIndex);
                }
                vertices[vertexIndexOld].linkedFaces.Clear();
            }

            public Vector3 CalculateVertexNormal(int vertexIndex)
            {
                Vertex vert = vertices[vertexIndex];
                Vector3 n = Vector3.zero;
                IndexList linkedFaces = vert.linkedFaces;
                int num = linkedFaces.Count;
                for (int i = 0; i < num; ++i)
                {
                    int faceIndex = linkedFaces[i];
                    n += faces[faceIndex].normal;
                }
                NormalizeSmallVector(ref n);
                vert.normal = n;
                return n;
            }

            public void CalculateVertexNormals()
            {
                int numVerts = vertCount();
                for (int i = 0; i < numVerts; ++i)
                {
                    CalculateVertexNormal(i);
                }
            }

            // VERTEXPAIRS //

            public bool isVertexPairValid(VertexPair vp)
            {
                if (vp.v[0] == vp.v[1]) return false;
                if (IsVertexValid(vp.v[0]) == false) return false;
                if (IsVertexValid(vp.v[1]) == false) return false;
                return true;
            }

            public Vector3 CalculateVertexPairCenter(VertexPair vp)
            {
                return 0.5f * (vertices[vp.v[0]].coords + vertices[vp.v[1]].coords);
            }

            // FACES //

            public int faceCount() { return faces.Count; }

            public int AddFace(Face f)
            {
                int faceIndex = faces.Count;
                faces.Add(f);

                topology = MeshTopology.Mixed;

                for (int i = 0; i < f.cornerCount; ++i)
                {
                    vertices[f.v[i]].linkedFaces.Add(faceIndex);
                }
                numValidFaces++;
                return faceIndex;
            }

            public void UnlinkFace(int faceIndex)
            {
                Face f = faces[faceIndex];
                if (!f.valid)
                {
                    //				Debug.LogError("Unlinking invalid face!");
                }
                else
                {
                    numValidFaces--;
                    f.valid = false;

                    for (int i = 0; i < f.cornerCount; ++i)
                    {
                        vertices[f.v[i]].linkedFaces.Remove(faceIndex);
                    }
                }
            }

            public Vector3 CalculateFaceNormal(int faceIndex)
            {
                int i;
                Face face = faces[faceIndex];
                int vertCount = face.cornerCount;
                int[] vindex = face.v;
                Vector3[] vec = new Vector3[vertCount + 1];

                for (i = 0; i < vertCount - 1; ++i) vec[i] = vertices[vindex[i + 1]].coords - vertices[vindex[i]].coords;
                vec[i] = vertices[vindex[0]].coords - vertices[vindex[i]].coords;
                Vector3 normal = Vector3.zero;
                if (vertCount == 3)
                {
                    normal = Vector3.Cross(vec[0], vec[1]);
                }
                else
                {
                    // Sum normals based on consequtive pairs of edges
                    vec[vertCount] = vec[0];
                    for (i = 0; i < vertCount; ++i)
                    {
                        normal += Vector3.Cross(vec[i], vec[i + 1]);
                    }
                }
                NormalizeSmallVector(ref normal);
                face.normal = normal;
                return face.normal;
            }

            public void CalculateFaceNormals()
            {
                int numFaces = faces.Count;
                for (int i = 0; i < numFaces; ++i)
                {
                    CalculateFaceNormal(i);
                }
            }

            public Vector3 CalculateFaceCenter(int faceIndex)
            {
                Face f = faces[faceIndex];
                Vector3 result = Vector3.zero;
                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    result += vertices[f.v[cornerIndex]].coords;
                }
                return result * (1.0f / ((float)f.cornerCount));
            }

            public Vector2 CalculateFaceCenterUV1(int faceIndex)
            {
                Face f = faces[faceIndex];
                Vector2 result = Vector2.zero;
                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    result += f.uv1[cornerIndex];
                }
                return result * (1.0f / ((float)f.cornerCount));
            }

            public Vector2 CalculateFaceCenterUV2(int faceIndex)
            {
                Face f = faces[faceIndex];
                Vector2 result = Vector2.zero;
                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    result += f.uv2[cornerIndex];
                }
                return result * (1.0f / ((float)f.cornerCount));
            }

            public Vector3 CalculateFaceCenterVertexNormal(int faceIndex)
            {
                Face f = faces[faceIndex];
                Vector3 result = Vector3.zero;
                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    result += f.vertexNormal[cornerIndex];
                }
                return result * (1.0f / ((float)f.cornerCount));
            }

            public BoneWeight CalculateFaceCenterBoneWeight(int faceIndex)
            {
                Face f = faces[faceIndex];
                Dictionary<int, float> resultWeightForBone = new Dictionary<int, float>(4 * f.cornerCount);

                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    Vertex v = vertices[f.v[cornerIndex]];
                    BoneWeight bw = v.boneWeight;

                    int[] indexes = { bw.boneIndex0, bw.boneIndex1, bw.boneIndex2, bw.boneIndex3 };
                    float[] weights = { bw.weight0, bw.weight1, bw.weight2, bw.weight3 };

                    for (int i = 0; i < 4; ++i)
                    {
                        if (resultWeightForBone.ContainsKey(indexes[i]))
                        {
                            resultWeightForBone[indexes[i]] += weights[i];
                        }
                        else
                        {
                            resultWeightForBone.Add(indexes[i], weights[i]);
                        }
                    }
                }

                // Sort by weight
                List<KeyValuePair<int, float>> mList = new List<KeyValuePair<int, float>>(resultWeightForBone);

                mList.Sort((x, y) => y.Value.CompareTo(x.Value));
                // Make sure there are at least 4 entries
                while (mList.Count < 4) mList.Add(new KeyValuePair<int, float>(0, 0.0f));

                float weightSum = mList[0].Value + mList[1].Value + mList[2].Value + mList[3].Value;
                float weightFact = 1.0f;
                if (weightSum != 0.0f) weightFact = 1.0f / weightSum;

                BoneWeight result = new BoneWeight();
                result.boneIndex0 = mList[0].Key;
                result.weight0 = mList[0].Value * weightFact;
                result.boneIndex1 = mList[1].Key;
                result.weight1 = mList[1].Value * weightFact;
                result.boneIndex2 = mList[2].Key;
                result.weight2 = mList[2].Value * weightFact;
                result.boneIndex3 = mList[3].Key;
                result.weight3 = mList[3].Value * weightFact;

                return result;
            }

            public Color CalculateFaceCenterColor(int faceIndex)
            {
                Face f = faces[faceIndex];
                Color result = Color.clear;
                for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    result += vertices[f.v[cornerIndex]].color;
                }
                return result * (1.0f / ((float)f.cornerCount));
            }

            public float CalculateFaceArea(int faceIndex)
            {
                Face f = faces[faceIndex];
                if (f.cornerCount != 3)
                {
                    Debug.LogError("Currently only the area of triangles can be calculated.");
                }

                Vector3 a = vertices[f.v[1]].coords - vertices[f.v[0]].coords;
                Vector3 b = vertices[f.v[2]].coords - vertices[f.v[0]].coords;
                float area = Vector3.Cross(a, b).magnitude;
                area = Mathf.Abs(0.5f * area);
                return area;
            }

            public float CornerAngle(int faceIndex, int cornerIndex)
            {
                Face f = faces[faceIndex];
                int[] fv = f.v;
                int vertCount = f.cornerCount;
                int prev = cornerIndex - 1;
                int next = cornerIndex + 1;
                if (prev < 0) prev = vertCount - 1;
                else if (next >= vertCount) next = 0;

                Vector3 p = vertices[fv[cornerIndex]].coords;
                Vector3 a = vertices[fv[prev]].coords - p;
                Vector3 b = vertices[fv[next]].coords - p;
                return Vector3.Angle(a, b);
            }

            // ALGORITHMS

            public virtual void Clear()
            {
                vertices.Clear();
                faces.Clear();
                numValidVerts = 0;
                numValidFaces = 0;
                numMaterials = 1;
                hasVertexColors = false;
                hasBoneWeights = false;
                topology = MeshTopology.Mixed;
                hasUV1 = false;
                hasUV2 = false;
                equalityTolerance = 0.0f;

                bindposes = null;
                calculateTangents = true;
            }

            public MeshTopology DetermineMeshTopology()
            {
                if (faces.Count == 0)
                {
                    topology = MeshTopology.Mixed;
                }
                else
                {
                    Face f0 = faces[0];
                    int f0CornerCount = f0.cornerCount;
                    int faceCount = faces.Count;
                    int faceIndex = 1;
                    for (; faceIndex < faceCount; ++faceIndex)
                    {
                        if (faces[faceIndex].cornerCount != f0CornerCount) break;
                    }
                    if (faceIndex == faceCount)
                    {
                        if (f0CornerCount == 4) topology = MeshTopology.Quads;
                        else topology = MeshTopology.Triangles;
                    }
                    else
                    {
                        topology = MeshTopology.Mixed;
                    }
                }
                return topology;
            }

            // collects the vertices that are connected to vertexIndex
            // TODO: optimize .. store Face array and get neighbours via switch for quads
            public void CollectVerticesAroundVertex(int vertexIndex, ref List<int> list)
            {
                int mark = GetUniqueTag();
                IndexList surroundingFaces = vertices[vertexIndex].linkedFaces; // surrounding faces
                int numFaces = surroundingFaces.Count;

                if (topology == MeshTopology.Triangles)
                {
                    vertices[vertexIndex].mark = mark;
                    for (int i = 0; i < numFaces; ++i)
                    {
                        Face face = faces[surroundingFaces[i]];
                        for (int cornerIndex = 0; cornerIndex < 3; ++cornerIndex)
                        { // three points!
                            int vertIndex = face.v[cornerIndex];
                            if (vertices[vertIndex].mark != mark)
                            {
                                list.Add(vertIndex);
                                vertices[vertIndex].mark = mark;
                            }
                        }
                    }
                }
                else
                {
                    // For n-gons collect prev and next corner vertices
                    for (int i = 0; i < numFaces; ++i)
                    {
                        Face face = faces[surroundingFaces[i]];
                        int next = 0;
                        int corner = face.cornerCount - 1;
                        int prev = face.cornerCount - 2;
                        for (; next < face.cornerCount; ++next)
                        {
                            if (face.v[corner] == vertexIndex)
                            {
                                int vert = face.v[next];
                                if (vertices[vert].mark != mark)
                                {
                                    list.Add(vert);
                                    vertices[vert].mark = mark;
                                }
                                vert = face.v[prev];
                                if (vertices[vert].mark != mark)
                                {
                                    list.Add(vert);
                                    vertices[vert].mark = mark;
                                }
                                break;
                            }
                            prev = corner;
                            corner = next;
                        }
                    }
                }
            }

            // What this basically does is search for common faces in the two linkedFaces lists
            // It uses & destroys face marks
            public void CollectVertexPairFaces(VertexPair pair, IndexList commonFaces)
            {
                IndexList il0 = vertices[pair.v[0]].linkedFaces; // surrounding faces
                IndexList il1 = vertices[pair.v[1]].linkedFaces; // surrounding faces		
                int[] v0Faces = il0.array;
                int[] v1Faces = il1.array;
                int v0Count = il0.Count;
                int v1Count = il1.Count;
                int tag = GetUniqueTag();

                commonFaces.GrowToCapacity(v1Count);
                for (int i = 0; i < v0Count; ++i) faces[v0Faces[i]].mark = tag;
                for (int i = 0; i < v1Count; ++i)
                {
                    int faceIndex = v1Faces[i];
                    if (faces[faceIndex].mark == tag)
                    {
                        commonFaces.AddUnsafe(faceIndex);
                    }
                }
            }

            public void CollectCollapseFacesForVertexPair(VertexPair pair, IndexList changeFaces0, IndexList changeFaces1, IndexList commonFaces)
            {
                IndexList il0 = vertices[pair.v[0]].linkedFaces;
                IndexList il1 = vertices[pair.v[1]].linkedFaces;
                int[] v0Faces = il0.array;
                int[] v1Faces = il1.array;
                int v0Count = il0.Count;
                int v1Count = il1.Count;
                int tag = GetUniqueTag();

                // Grow target lists to save on checks later
                changeFaces0.GrowToCapacity(v0Count);
                changeFaces1.GrowToCapacity(v1Count);
                commonFaces.GrowToCapacity(v0Count); // could be min(v0count, v1count), but that's probably slower

                for (int i = 0; i < v1Count; ++i) faces[v1Faces[i]].mark = tag;
                for (int i = 0; i < v0Count; ++i)
                {
                    int faceIndex = v0Faces[i];
                    Face f = faces[faceIndex];
                    //	if (f.valid) {
                    if (f.mark == tag)
                    {
                        commonFaces.AddUnsafe(faceIndex);
                        f.mark = 0;
                    }
                    else
                    {
                        changeFaces0.AddUnsafe(faceIndex);
                    }
                    //	}
                }
                for (int i = 0; i < v1Count; ++i)
                {
                    int faceIndex = v1Faces[i];
                    Face f = faces[faceIndex];
                    if (/*f.valid &&*/ f.mark == tag)
                    {
                        changeFaces1.AddUnsafe(faceIndex);
                    }
                }
            }

            IndexList changeFaces0 = new IndexList(32);
            IndexList changeFaces1 = new IndexList(32);
            IndexList removeFaces = new IndexList(32);


            public int CollapseVertexPair(CollapseInfo info)
            {
                if (topology != MeshTopology.Triangles)
                {
                    Debug.LogError("LodlMesh: Collapsing a vertex pair requires a triangle mesh");
                    return 0;
                }

                VertexPair pair = info.vp;
                int vindex0 = pair.v[0];
                int vindex1 = pair.v[1];
                Vertex vertex0 = vertices[vindex0];
                Vertex vertex1 = vertices[vindex1];
                int i, j;

                changeFaces0.Clear();
                changeFaces1.Clear();
                removeFaces.Clear();
                CollectCollapseFacesForVertexPair(pair, changeFaces0, changeFaces1, removeFaces);

                // Adjust parameters of vertex0 to the new position
                float ratio1 = info.Ratio(this);

                // try baricentric projection on all the removeFaces (usually 2)
                int projFaceIndex = -1;
                Face projFace = null;
                int projCorner0 = 0, projCorner1 = 0;
                Vector3 bari = Vector3.zero;
                int[] v = null;
                for (i = 0; i < removeFaces.Count; ++i)
                {
                    Face f = faces[removeFaces[i]];
                    v = f.v;
                    bari = BaricentricProjection(info.targetPosition, vertices[v[0]].coords, vertices[v[1]].coords, vertices[v[2]].coords);
                    if (AreBaricentricCoordsInsideTriangle(bari))
                    {
                        projFaceIndex = removeFaces[i];
                        projFace = f;
                        projCorner0 = projFace.CornerIndexTriangle(vindex0);
                        projCorner1 = projFace.CornerIndexTriangle(vindex1);
                        break;
                    }
                }
                // There must not be invalid faces in changeFaces0 or changeFaces1 !!!
                /*	for (i = 0; i < changeFaces0.Count; ++i) if (faces[changeFaces0[i]].valid == false) Debug.LogError("NOOO!");
                    for (i = 0; i < changeFaces1.Count; ++i) if (faces[changeFaces1[i]].valid == false) Debug.LogError("NOOO!");
                    for (i = 0; i < removeFaces.Count; ++i) if (faces[removeFaces[i]].valid == false) Debug.LogError("NOOO!");
                    */
                // Deal with vertex colors and boneweights. these are per vertex.
                if (projFace != null)
                {
                    if (hasVertexColors) vertex0.color = bari.x * vertices[v[0]].color + bari.y * vertices[v[1]].color + bari.z * vertices[v[2]].color;
                    if (hasBoneWeights) vertex0.boneWeight = BoneWeightBaricentricInterpolation(vertices[v[0]].boneWeight, vertices[v[1]].boneWeight, vertices[v[2]].boneWeight, bari.x, bari.y, bari.z);
                }
                else
                {
                    if (hasVertexColors) vertex0.color = Color.Lerp(vertex0.color, vertex1.color, ratio1);
                    if (hasBoneWeights) vertex0.boneWeight = BoneWeightLerp(vertex0.boneWeight, vertex1.boneWeight, ratio1);
                }

                // Determine corner numbers for v0 in changefaces0 and v1 in changefaces1
                IndexList corners0 = new IndexList(changeFaces0.Count);
                for (i = 0; i < changeFaces0.Count; ++i) corners0[i] = faces[changeFaces0[i]].CornerIndexTriangle(vindex0);
                IndexList corners1 = new IndexList(changeFaces1.Count);
                for (i = 0; i < changeFaces1.Count; ++i) corners1[i] = faces[changeFaces1[i]].CornerIndexTriangle(vindex1);

                #region Face-Dependent Attributes (Vertex normals, uv1, uv2)

                // NORMALS
                int count = 0, filterTag = GetUniqueTag();
                Vector3 projNormalNew = Vector3.zero;
                if (projFace != null)
                {
                    projNormalNew = bari.x * projFace.vertexNormal[0] + bari.y * projFace.vertexNormal[1] + bari.z * projFace.vertexNormal[2];
                    count = _replaceCornerNormalInFaceGroup(projFace.vertexNormal[projCorner0], projNormalNew, changeFaces0, corners0, filterTag);
                }
                if (count < changeFaces0.Count)
                {
                    // there are faces which cannot use baricentric projection
                    for (j = 0; j < removeFaces.Count; ++j)
                    {
                        if (removeFaces[j] != projFaceIndex)
                        {
                            Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                            Vector3 oldNormal = f2.vertexNormal[c0];
                            _replaceCornerNormalInFaceGroup(oldNormal, Vector3.Lerp(oldNormal, f2.vertexNormal[c1], ratio1), changeFaces0, corners0, filterTag);
                        }
                    }
                }

                count = 0; filterTag = GetUniqueTag();
                if (projFace != null)
                {
                    count = _replaceCornerNormalInFaceGroup(projFace.vertexNormal[projCorner1], projNormalNew, changeFaces1, corners1, filterTag);
                }
                if (count < changeFaces1.Count)
                {
                    // there are faces which cannot use baricentric projection
                    for (j = 0; j < removeFaces.Count; ++j)
                    {
                        if (removeFaces[j] != projFaceIndex)
                        {
                            Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                            Vector3 oldNormal = f2.vertexNormal[c1];
                            _replaceCornerNormalInFaceGroup(oldNormal, Vector3.Lerp(f2.vertexNormal[c0], oldNormal, ratio1), changeFaces1, corners1, filterTag);
                        }
                    }
                }

                if (hasUV1)
                {
                    count = 0; filterTag = GetUniqueTag();
                    Vector2 projUV1New = Vector2.zero;
                    if (projFace != null)
                    {
                        projUV1New = bari.x * projFace.uv1[0] + bari.y * projFace.uv1[1] + bari.z * projFace.uv1[2];
                        count = _replaceCornerUV1InFaceGroup(projFace.uv1[projCorner0], projUV1New, changeFaces0, corners0, filterTag);
                    }
                    if (count < changeFaces0.Count)
                    {
                        // there are faces which cannot use baricentric projection
                        for (j = 0; j < removeFaces.Count; ++j)
                        {
                            if (removeFaces[j] != projFaceIndex)
                            {
                                Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                                Vector2 oldUV1 = f2.uv1[c0];
                                _replaceCornerUV1InFaceGroup(oldUV1, Vector2.Lerp(oldUV1, f2.uv1[c1], ratio1), changeFaces0, corners0, filterTag);
                            }
                        }
                    }

                    count = 0; filterTag = GetUniqueTag();
                    if (projFace != null)
                    {
                        count = _replaceCornerUV1InFaceGroup(projFace.uv1[projCorner1], projUV1New, changeFaces1, corners1, filterTag);
                    }
                    if (count < changeFaces1.Count)
                    {
                        // there are faces which cannot use baricentric projection
                        for (j = 0; j < removeFaces.Count; ++j)
                        {
                            if (removeFaces[j] != projFaceIndex)
                            {
                                Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                                Vector2 oldUV1 = f2.uv1[c1];
                                _replaceCornerUV1InFaceGroup(oldUV1, Vector2.Lerp(f2.uv1[c0], oldUV1, ratio1), changeFaces1, corners1, filterTag);
                            }
                        }
                    }
                }

                if (hasUV2)
                {
                    count = 0; filterTag = GetUniqueTag();
                    Vector2 projUV2New = Vector2.zero;
                    if (projFace != null)
                    {
                        projUV2New = bari.x * projFace.uv2[0] + bari.y * projFace.uv2[1] + bari.z * projFace.uv2[2];
                        count = _replaceCornerUV2InFaceGroup(projFace.uv2[projCorner0], projUV2New, changeFaces0, corners0, filterTag);
                    }
                    if (count < changeFaces0.Count)
                    {
                        // there are faces which cannot use baricentric projection
                        for (j = 0; j < removeFaces.Count; ++j)
                        {
                            if (removeFaces[j] != projFaceIndex)
                            {
                                Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                                Vector2 oldUV2 = f2.uv2[c0];
                                _replaceCornerUV2InFaceGroup(oldUV2, Vector2.Lerp(oldUV2, f2.uv2[c1], ratio1), changeFaces0, corners0, filterTag);
                            }
                        }
                    }

                    count = 0; filterTag = GetUniqueTag();
                    if (projFace != null)
                    {
                        count = _replaceCornerUV2InFaceGroup(projFace.uv2[projCorner1], projUV2New, changeFaces1, corners1, filterTag);
                    }
                    if (count < changeFaces1.Count)
                    {
                        // there are faces which cannot use baricentric projection
                        for (j = 0; j < removeFaces.Count; ++j)
                        {
                            if (removeFaces[j] != projFaceIndex)
                            {
                                Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1);
                                Vector2 oldUV2 = f2.uv2[c1];
                                _replaceCornerUV2InFaceGroup(oldUV2, Vector2.Lerp(f2.uv2[c0], oldUV2, ratio1), changeFaces1, corners1, filterTag);
                            }
                        }
                    }
                }
                #endregion
                // Move vertex to goal position
                vertex0.coords = info.targetPosition;

                // remove faces
                //	Debug.Log("Change faces 1 num: " + changeFaces0.Count);
                //	Debug.Log("Change faces 2 num: " + changeFaces1.Count);
                //	Debug.Log("Remove faces num: " + removeFaces.Count);
                for (i = 0; i < removeFaces.Count; ++i)
                {
                    UnlinkFace(removeFaces[i]);
                }

                // change vertex on vindex1 faces, update surrounding faces on vindex0
                for (i = 0; i < changeFaces1.Count; ++i)
                {
                    int faceIndex = changeFaces1[i];
                    Face f = faces[faceIndex];
                    if (f.valid)
                    {
                        f.ReplaceVertex(vindex1, vindex0);
                        vertex0.linkedFaces.Add(faceIndex);
                    }
                }

                // mark vindex1 as invalid
                vertex1.linkedFaces.Clear();
                if (vertex1.valid == true)
                {
                    numValidVerts--;
                    vertex1.valid = false;
                }
                else
                {
                    Debug.LogError("vindex1 was already invalid");
                }
                return vindex0;
            }

            int _replaceCornerNormalInFaceGroup(Vector3 oldNormal, Vector3 newNormal, IndexList faceList, IndexList corners, int filterTag)
            {
                int count = 0;
                for (int i = 0; i < faceList.Count; ++i)
                {
                    Face f = faces[faceList[i]];
                    if (f.mark == filterTag) continue;
                    if (Vector3CompareWithTolerance(f.vertexNormal[corners[i]], oldNormal, equalityTolerance) == 0)
                    {
                        f.vertexNormal[corners[i]] = newNormal;
                        f.mark = filterTag;
                        count++;
                    }
                }
                return count;
            }

            int _replaceCornerUV1InFaceGroup(Vector2 oldUV1, Vector2 newUV1, IndexList faceList, IndexList corners, int filterTag)
            {
                int count = 0;
                for (int i = 0; i < faceList.Count; ++i)
                {
                    Face f = faces[faceList[i]];
                    if (f.mark == filterTag) continue;
                    if (Vector2CompareWithTolerance(f.uv1[corners[i]], oldUV1, equalityTolerance) == 0)
                    {
                        f.uv1[corners[i]] = newUV1;
                        f.mark = filterTag;
                        count++;
                    }
                }
                return count;
            }

            int _replaceCornerUV2InFaceGroup(Vector2 oldUV2, Vector2 newUV2, IndexList faceList, IndexList corners, int filterTag)
            {
                int count = 0;
                for (int i = 0; i < faceList.Count; ++i)
                {
                    Face f = faces[faceList[i]];
                    if (f.mark == filterTag) continue;
                    if (Vector2CompareWithTolerance(f.uv2[corners[i]], oldUV2, equalityTolerance) == 0)
                    {
                        f.uv2[corners[i]] = newUV2;
                        f.mark = filterTag;
                        count++;
                    }
                }
                return count;
            }

            public int InvalidateUnconnectedVertices()
            {
                int vertCount = vertices.Count;
                int count = 0;
                for (int i = 0; i < vertCount; ++i)
                {
                    Vertex v = vertices[i];
                    if (v.valid)
                    {
                        if (v.linkedFaces.Count == 0)
                        {
                            //Debug.Log("Removed vertex without faces");
                            v.valid = false;
                            numValidVerts--;
                            count++;
                        }
                    }
                }
                return count;
            }

            public int InvalidateDegenerateFaces()
            {
                int faceCount = faces.Count;
                int count = 0;
                for (int faceIndex = 0; faceIndex < faceCount; ++faceIndex)
                {
                    Face f = faces[faceIndex];
                    if (f.valid)
                    {
                        int[] v = f.v;
                        int numCorners = f.cornerCount;
                        for (int j = 0; j < numCorners; ++j)
                        {
                            if (IsVertexValid(v[j]) == false)
                            {
                                UnlinkFace(faceIndex);
                                count++;
                                break; // j!
                            }
                            for (int k = j + 1; k < numCorners; ++k)
                            {
                                if (v[j] == v[k])
                                {
                                    UnlinkFace(faceIndex);
                                    count++;
                                    j = numCorners; break;
                                }
                            }
                        }
                        // todo? maybe get rid of faces with area 0?
                    }
                }
                return count;
            }

            public void RebuildMesh(ref int[] vertTable, ref int[] faceTable, bool verbose = false)
            {
                InvalidateUnconnectedVertices();
                InvalidateDegenerateFaces();
                int numVerts = vertCount();
                int numFaces = faceCount();

                vertTable = new int[numVerts];
                faceTable = new int[numFaces];
                List<Vertex> newVerts = new List<Vertex>();
                List<Face> newFaces = new List<Face>();

                for (int i = 0; i < numVerts; ++i)
                {
                    if (vertices[i].valid)
                    {
                        vertTable[i] = newVerts.Count;
                        newVerts.Add(vertices[i]);
                    }
                    else
                    {
                        vertTable[i] = -1;
                    }
                }
                for (int i = 0; i < numFaces; ++i)
                {
                    if (faces[i].valid)
                    {
                        faceTable[i] = newFaces.Count;
                        newFaces.Add(faces[i]);
                    }
                    else
                    {
                        faceTable[i] = -1;
                    }
                }
                vertices = newVerts;
                faces = newFaces;
                // Update index
                if (verbose)
                {
                    Debug.Log("Rebuild Vertex Count " + numVerts + " -> " + vertCount());
                    Debug.Log("Rebuild Face Count " + numFaces + " -> " + faceCount());
                }
                numVerts = vertCount();
                numFaces = faceCount();
                for (int i = 0; i < numVerts; ++i)
                {
                    IndexList lFaces = vertices[i].linkedFaces;
                    IndexList lFacesNew = new IndexList(18);
                    for (int j = 0; j < lFaces.Count; ++j)
                    {
                        int l = faceTable[lFaces[j]];
                        if (l != -1) lFacesNew.Add(l);
                        else Debug.LogError("!!!!!");
                    }
                    vertices[i].linkedFaces = lFacesNew;
                }
                for (int i = 0; i < numFaces; ++i)
                {
                    int[] v = faces[i].v;
                    for (int j = 0; j < faces[i].cornerCount; ++j)
                    {
                        v[j] = vertTable[v[j]];
                    }
                }
                numValidVerts = numVerts;
                numValidFaces = numFaces;
            }

            public void RebuildVertexLinkedFaces()
            {
                int numVerts = vertCount();
                int numFaces = faceCount();

                for (int vertexIndex = 0; vertexIndex < numVerts; ++vertexIndex)
                {
                    vertices[vertexIndex].linkedFaces.Clear();
                }
                for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex)
                {
                    Face f = faces[faceIndex];
                    for (int cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                    {
                        vertices[f.v[cornerIndex]].linkedFaces.Add(faceIndex);
                    }
                }
            }

        }
        /// <summary>
        /// A polygonal mesh with edge information.
        /// </summary>
        private class MeshEdges : BaseMesh
        {
            public List<Edge> edges = null;
            protected List<int>[] linkedEdges = null; // List of edge indexes linked to each vertex

            public int edgeCount() { return (edges != null) ? edges.Count : -1; }
            public List<int> linkedEdgesForVert(int vertexIndex) { return linkedEdges[vertexIndex]; }

            public void AddEdge(Edge e)
            {
                edges.Add(e);
            }

            public bool IsEdgeValid(int edgeIndex)
            {
                Edge e = edges[edgeIndex];
                if (e.linkedFaces.Count == 0) return false;
                return isVertexPairValid(e);
            }

            public float EdgeLength(int edgeIndex)
            {
                int[] v = edges[edgeIndex].v;
                return Vector3.Distance(vertices[v[0]].coords, vertices[v[1]].coords);
            }

            public float EdgeLengthSqr(int edgeIndex)
            {
                int[] v = edges[edgeIndex].v;
                return (vertices[v[0]].coords - vertices[v[1]].coords).sqrMagnitude;
            }

            public void UnlinkEdge(int edgeIndex)
            {
                if (IsEdgeValid(edgeIndex))
                {
                }
                else
                {
                    Edge e = edges[edgeIndex];
                    for (int i = 0; i < 2; ++i)
                    {
                        linkedEdges[e.v[i]].Remove(edgeIndex);
                    }
                    e.Invalidate();
                }
            }

            public void ClearEdges()
            {
                edges = null;
                linkedEdges = null;
            }

            public bool IsEdgeBorder(int edgeIndex)
            {
                return (edges[edgeIndex].linkedFaces.Count == 1);
            }

            // TODO: do this better with one call to determine various edge flags
            // Requires edges.linkedfaces to be valid!
            public bool IsEdgeUV1Seam(int edgeIndex)
            {
                Edge e = edges[edgeIndex];
                IndexList linkedFaces = e.linkedFaces;
                switch (linkedFaces.Count)
                {
                    case 0:
                    case 1:
                        return false;
                    case 2:
                        // Check if the verts have the same uv2 coords on both faces
                        Face f0 = faces[linkedFaces[0]];
                        Face f1 = faces[linkedFaces[1]];
                        int vi0 = e.v[0];
                        //	Debug.Log("Edge " + e.v[0] + "->" + e.v[1]);
                        //	Debug.Log("Face0 n " + f0.cornerCount + " " + f0.v[0] + " " + f0.v[1] + " " + f0.v[2] + " " + f0.v[3]);
                        if (f0.uv1[f0.CornerIndex(vi0)] != f1.uv1[f1.CornerIndex(vi0)]) return true;
                        int vi1 = e.v[1];
                        //	Debug.Log("Face1 n + " + f1.cornerCount + " " + f1.v[0] + " " + f1.v[1] + " " + f1.v[2] + " " + f1.v[3]);
                        return (f0.uv1[f0.CornerIndex(vi1)] != f1.uv1[f1.CornerIndex(vi1)]);
                    default: // > 2
                        return true;
                }
            }

            // requires edges.linkedfaces to be valid
            public bool IsEdgeUV2Seam(int edgeIndex)
            {
                Edge e = edges[edgeIndex];
                IndexList linkedFaces = e.linkedFaces;
                switch (linkedFaces.Count)
                {
                    case 0:
                    case 1:
                        return false;
                    case 2:
                        // Check if the verts have the same uv2 coords on both faces
                        Face f0 = faces[linkedFaces[0]];
                        Face f1 = faces[linkedFaces[1]];
                        int vi0 = e.v[0];
                        if (f0.uv2[f0.CornerIndex(vi0)] != f1.uv2[f1.CornerIndex(vi0)]) return true;
                        int vi1 = e.v[1];
                        return (f0.uv2[f0.CornerIndex(vi1)] != f1.uv2[f1.CornerIndex(vi1)]);
                    default:
                        return true;
                }
            }

            // requires edges.linkedfaces to be valid
            public bool isEdgeMaterialSeam(int edgeIndex)
            {
                IndexList linkedFaces = edges[edgeIndex].linkedFaces;
                if (linkedFaces.Count < 2) return false;

                int mat = faces[linkedFaces[0]].material;
                for (int i = 1; i < linkedFaces.Count; ++i)
                {
                    if (mat != faces[linkedFaces[i]].material) return true;
                }
                return false;
            }

            // ALGORITHMS //

            public int EdgeIndexForVertices(int vindex0, int vindex1)
            {
                int edgeIndex;
                List<int> linkedEdgeIndex = linkedEdges[vindex0];
                int count = linkedEdgeIndex.Count;

                for (int i = 0; i < count; ++i)
                {
                    edgeIndex = linkedEdgeIndex[i];
                    if (edges[edgeIndex].OtherVertex(vindex0) == vindex1) return edgeIndex;
                }
                //	Debug.LogError("Bad bad bad. Linked edge that doesn't contain the linked vertex v0:" + vindex0 + " v1: " + vindex1 + " linkedEdgeCountV0: " + linkedEdgeIndex.Count);
                return -1;
            }

            public void GenerateEdgeTopology()
            {
                CalculateEdgeLinkedFaces();
            }

            public void GenerateEdgeList()
            {
                int numVerts = vertCount();

                // alloc new arrays, let gc do the cleanup
                edges = new List<Edge>();

                linkedEdges = new List<int>[numVerts];
                for (int i = 0; i < numVerts; ++i)
                {
                    linkedEdges[i] = new List<int>();
                }

                List<int> surroundingVerts = new List<int>();
                for (int i = 0; i < numVerts; ++i)
                {
                    surroundingVerts.Clear();
                    CollectVerticesAroundVertex(i, ref surroundingVerts);
                    int count = surroundingVerts.Count;
                    for (int j = 0; j < count; ++j)
                    {
                        int vertexIndex = surroundingVerts[j];
                        if (i < vertexIndex)
                        {
                            Edge edge = new Edge(i, vertexIndex);
                            int edgeIndex = edges.Count;
                            linkedEdges[i].Add(edgeIndex);
                            linkedEdges[vertexIndex].Add(edgeIndex);
                            edges.Add(edge);
                        }
                    }
                }
            }

            public void RegenerateVertexLinkedEdges()
            {
                int numVerts = vertCount();
                int numEdges = edges.Count;
                linkedEdges = new List<int>[numVerts];
                for (int vertIndex = 0; vertIndex < numVerts; ++vertIndex)
                {
                    linkedEdges[vertIndex] = new List<int>();
                }

                for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex)
                {
                    int[] vi = edges[edgeIndex].v;
                    linkedEdges[vi[0]].Add(edgeIndex);
                    linkedEdges[vi[1]].Add(edgeIndex);
                }
            }

            public void CalculateEdgeLinkedFaces()
            {
                int numEdges = edgeCount();
                for (int i = 0; i < numEdges; ++i)
                {
                    Edge edge = edges[i];
                    edge.linkedFaces.Clear();
                    CollectVertexPairFaces(edge, edge.linkedFaces);
                    /*	if (edge.linkedFaces.Count > 2) {
                            Debug.Log("WARNING! An edge has " + edge.linkedFaces.Count + " faces!");
                        }*/
                }
            }

            public List<int>[] CalculateFaceLinkedEdges()
            {
                // Needs linkedFaces on edges to be valid !!!!
                int numFaces = faceCount();
                List<int>[] result = new List<int>[numFaces];
                for (int i = 0; i < numFaces; ++i)
                {
                    result[i] = new List<int>();
                }

                int numEdges = edgeCount();
                for (int i = 0; i < numEdges; ++i)
                {
                    IndexList edgeFaces = edges[i].linkedFaces;
                    for (int j = 0; j < edgeFaces.Count; ++j)
                    {
                        result[edgeFaces[j]].Add(i);
                    }
                }
                return result;
            }

            void _markGroupFaceNeightbours(int vertexIndex, int faceIndex, ref List<int>[] faceLinkedEdges, int groupIndex)
            {
                Stack<int> stack = new Stack<int>();
                stack.Push(faceIndex);
                do
                {
                    faceIndex = stack.Pop();
                    Face f = faces[faceIndex];
                    if (f.mark == -1)
                    {
                        f.mark = groupIndex; // mark face group. -1 = available
                                             // locate neighbours connected by non-creases
                        List<int> faceEdges = faceLinkedEdges[faceIndex];
                        // Search for other faces connected with non-crease edges
                        for (int e = 0; e < faceEdges.Count; ++e)
                        {
                            Edge edge = edges[faceEdges[e]];
                            if (IsEdgeValid(faceEdges[e]) && edge.crease < 1.0f && edge.ContainsVertex(vertexIndex))
                            {
                                IndexList edgeFaces = edge.linkedFaces;
                                for (int k = 0; k < edgeFaces.Count; ++k)
                                {
                                    int edgeFaceIndex = edgeFaces[k];
                                    if (faces[edgeFaceIndex].mark == -1)
                                    {
                                        stack.Push(edgeFaceIndex);
                                    }
                                }
                            }
                        }
                    }
                } while (stack.Count > 0);
            }

            public void CalculateFaceVertexNormalsFromEdgeCreasesForVertex(int vertexIndex, ref List<int>[] faceLinkedEdges)
            {
                int i, j, grp;
                List<int> grpCornerIndex = new List<int>();

                Vertex v = vertices[vertexIndex];
                IndexList vertexFaces = v.linkedFaces;
                int vertexFaceCount = vertexFaces.Count;
                //	List<int> vertexEdges = linkedEdges[vertexIndex];
                // Clear face marks around vertex
                for (j = 0; j < vertexFaceCount; ++j)
                {
                    faces[vertexFaces[j]].mark = -1; // TODO: could be faster with uniqueTag
                }
                // This will add each facemark to a groupIndex
                int groupIndex = 0;
                for (j = 0; j < vertexFaceCount; ++j)
                {
                    int faceIndex = vertexFaces[j];
                    if (faces[faceIndex].mark == -1)
                    { // face still available
                        _markGroupFaceNeightbours(vertexIndex, vertexFaces[j], ref faceLinkedEdges, groupIndex);
                        groupIndex++;
                    }
                }
                // Build group arrays
                List<int>[] groups = new List<int>[groupIndex]; // are these too many allocations?
                for (i = 0; i < groupIndex; ++i) groups[i] = new List<int>();
                for (i = 0; i < vertexFaceCount; ++i)
                {
                    int faceIndex = vertexFaces[i];
                    int mark = faces[faceIndex].mark;
                    groups[mark].Add(faceIndex);
                }

                // Calculate and set normal for each face on the vertex based on the groups
                Vector3 normal;
                for (grp = 0; grp < groupIndex; ++grp)
                {
                    normal = Vector3.zero;
                    List<int> grpFaces = groups[grp];
                    int cnt = grpFaces.Count;
                    grpCornerIndex.Clear();
                    for (i = 0; i < cnt; ++i)
                    {
                        Face f = faces[grpFaces[i]];
                        if (f.normal == Vector3.zero)
                        {
                            //	Debug.Log("face has zero normal .. valid " + f.valid);
                        }
                        // Multiply with corner angle (=SLOW?)
                        int corner = f.CornerIndex(vertexIndex);
                        float fact = CornerAngle(grpFaces[i], corner);
                        normal += f.normal * fact;
                        grpCornerIndex.Add(corner);
                    }
                    NormalizeSmallVector(ref normal);
                    if (normal == Vector3.zero)
                    {
                        //	Debug.Log("NORMAL == ZERO facecount " + cnt);
                    }
                    // Now set the normal to all group faces
                    for (i = 0; i < cnt; ++i)
                    {
                        faces[grpFaces[i]].vertexNormal[grpCornerIndex[i]] = normal;
                    }
                }
            }

            /// <summary>
            /// Calculates the face vertex normals based on the edge crease information. Edges marked as
            /// creases will get split normals.
            /// </summary>
            public void CalculateFaceVertexNormalsFromEdgeCreases()
            {
                CalculateFaceNormals(); // maybe not needed
                CalculateEdgeLinkedFaces(); // maybe not needed
                List<int>[] faceLinkedEdges = CalculateFaceLinkedEdges();
                int numVerts = vertCount();
                for (int vertexIndex = 0; vertexIndex < numVerts; ++vertexIndex)
                {
                    if (IsVertexValid(vertexIndex))
                    {
                        CalculateFaceVertexNormalsFromEdgeCreasesForVertex(vertexIndex, ref faceLinkedEdges);
                    }
                }
            }

            public new int CollapseVertexPair(CollapseInfo info)
            {
                VertexPair pair = info.vp;
                int v0 = pair.v[0];
                int v1 = pair.v[1];
                List<int> v0Edges = linkedEdges[v0];
                List<int> v1Edges = linkedEdges[v1];
                int i;
                // Update edges 
                // Mark the vertices that are connected by edges
                for (i = 0; i < v1Edges.Count; ++i)
                {
                    int edgeIndex = v1Edges[i];
                    int other = edges[edgeIndex].OtherVertex(v1);
                    vertices[other].mark = -1;
                }
                for (i = 0; i < v0Edges.Count; ++i)
                {
                    int edgeIndex = v0Edges[i];
                    int other = edges[edgeIndex].OtherVertex(v0);
                    vertices[other].mark = edgeIndex;
                }
                // now v1 verts that are only connected to v1 have value -1, double edge-connected verts have the edgeindex as mark				
                for (i = 0; i < v1Edges.Count; ++i)
                {
                    int edgeIndex = v1Edges[i];
                    if (vertices[edges[edgeIndex].OtherVertex(v1)].mark == -1)
                    {
                        edges[edgeIndex].ReplaceVertex(v1, v0);
                        if (IsEdgeValid(edgeIndex))
                        {
                            v0Edges.Add(edgeIndex);
                        }
                    }
                    else
                    {
                        Edge e1 = edges[edgeIndex];
                        int vindex = e1.OtherVertex(v1);
                        Edge e0 = edges[vertices[vindex].mark]; // vertex mark is edge index!
                                                                // There has to be another edge connecting v0 to vertex vindex
                        e0.crease = Mathf.Max(e1.crease, e0.crease); // keep the max crease value
                        UnlinkEdge(edgeIndex); // no more need for this!
                    }
                }
                // Remove invalid edges from mesh
                for (i = v0Edges.Count - 1; i >= 0; --i)
                { // backwards should be faster and i stays valid!
                    int edgeIndex = v0Edges[i];
                    if (IsEdgeValid(edgeIndex) == false)
                    {
                        UnlinkEdge(edgeIndex);
                        //v0Edges.Remove(edgeIndex);
                    }
                }

                // Deal with vertices and faces in baseclass
                info.vp = new VertexPair(v0, v1); // the original might have been invalidated THIS IS BAD
                base.CollapseVertexPair(info);
                v1Edges.Clear();

                // rebuild linkedfaces for the remaining edges
                for (i = 0; i < v0Edges.Count; ++i)
                {
                    Edge edge = edges[v0Edges[i]];
                    edge.linkedFaces.Clear();
                    CollectVertexPairFaces(edge, edge.linkedFaces);
                }

                return v0;
            }

            public void RebuildMesh(bool verbose = false)
            {
                /*		for (int i = 0; i < edgeCount; ++i) {
                            if (IsEdgeValid(i)) {
                                edgeTable[i] = newEdges.Count;
                                newEdges.Add(edges[i]);
                            }
                        } */

                int[] vertexTable = null;
                int[] faceTable = null; // not really used for now
                base.RebuildMesh(ref vertexTable, ref faceTable, verbose);

                int edgeCount = edges.Count;
                List<Edge> newEdges = new List<Edge>();
                //edges = newEdges;
                //edgeCount = edges.Count;
                for (int i = 0; i < edgeCount; ++i)
                {
                    Edge e = edges[i];
                    if (e.v[0] != e.v[1])
                    {
                        e.v[0] = vertexTable[e.v[0]];
                        e.v[1] = vertexTable[e.v[1]];
                        if (e.linkedFaces.Count > 0 && e.v[0] != -1 && e.v[1] != -1 && e.v[0] != e.v[1])
                        { // -1 is for invalid verts
                            newEdges.Add(e);
                        }
                    }
                    e.linkedFaces.Clear();
                }
                edges = newEdges;
                if (verbose)
                {
                    Debug.Log("Rebuild Edge Count " + edgeCount + " -> " + newEdges.Count);
                }

                RegenerateVertexLinkedEdges();
                CalculateEdgeLinkedFaces();
            }

            public float CalculateEdgeAngle(int edgeIndex)
            {
                IndexList edgefaces = edges[edgeIndex].linkedFaces;
                if (edgefaces.Count != 2) return 180.0f;
                Face f1 = faces[edgefaces[0]];
                Face f2 = faces[edgefaces[1]];
                return Vector3.Angle(f1.normal, f2.normal);
            }

            public bool CanEdgeBeDissolved(int edgeIndex)
            {
                // TODO!
                Edge e = edges[edgeIndex];
                // Don't dissolve creases
                if (e.crease == 1.0f) return false;
                // Only dissolve edges with two linked faces
                if (e.linkedFaces.Count != 2) return false;

                int f1index = e.linkedFaces[0];
                int f2index = e.linkedFaces[1];
                Face f1 = faces[f1index];
                Face f2 = faces[f2index];
                // Don't dissolve material borders
                if (f1.material != f2.material) return false;

                int f1corner1 = f1.CornerIndex(e.v[0]);
                int f1corner2 = f1.CornerIndex(e.v[1]);
                int f2corner1 = f2.CornerIndex(e.v[0]);
                int f2corner2 = f2.CornerIndex(e.v[1]);
                if (hasUV1)
                {
                    // Don't dissolve uv1 borders
                    if (f1.uv1[f1corner1] != f2.uv1[f2corner1]) return false;
                    if (f1.uv1[f1corner2] != f2.uv1[f2corner2]) return false;
                }
                if (hasUV2)
                {
                    if (f1.uv2[f1corner1] != f2.uv2[f2corner1]) return false;
                    if (f1.uv2[f1corner2] != f2.uv2[f2corner2]) return false;
                }
                return true;
            }

            // Dissolve an edge between two triangles.
            // the first is a quad afterwards and the second triangle is destroyed
            // expects valid input, no checks!
            public void DissolveEdgeTriangles(int edgeIndex)
            {
                int i;

                Edge e = edges[edgeIndex];
                // e needs to have two linked faces
                int f1index = e.linkedFaces[0];
                int f2index = e.linkedFaces[1];
                Face f1 = faces[f1index];
                Face f2 = faces[f2index];
                // f1 and f2 have two common vertices, need to be triangles
                int v2uniqueCorner = -1;
                for (i = 0; i < 3; ++i)
                {
                    if (e.ContainsVertex(f2.v[i]) == false)
                    {
                        v2uniqueCorner = i;
                        break;
                    }
                }
                for (i = 0; i < 3; ++i)
                {
                    if (e.ContainsVertex(f1.v[i]) == false)
                    {
                        int insertPos = i + 2;
                        if (insertPos >= 3) insertPos -= 3;

                        f1.cornerCount = 4; // f1 is now a quad
                        UnlinkFace(f2index);
                        vertices[f2.v[v2uniqueCorner]].linkedFaces.Add(f1index);
                        // Move verts backwards
                        for (int j = 3; j > insertPos; --j)
                        {
                            f1.CopyVertexInfoFromFace(f1, j - 1, j);
                        }
                        f1.CopyVertexInfoFromFace(f2, v2uniqueCorner, insertPos);
                        e.linkedFaces.Clear();
                        break;
                    }
                }
            }

            public override void Clear()
            {
                ClearEdges();
                base.Clear();
            }
        }




        private static BoneWeight BoneWeightLerp(BoneWeight a, BoneWeight b, float fact)
        {
            if (fact <= 0.0f) return a;
            else if (fact >= 1.0f) return b;

            float afact = 1.0f - fact;

            Dictionary<int, float> resultWeightForBone = new Dictionary<int, float>(8);
            {
                int[] indexes = {   a.boneIndex0, a.boneIndex1, a.boneIndex2, a.boneIndex3,
                                     b.boneIndex0, b.boneIndex1, b.boneIndex2, b.boneIndex3};
                float[] weights = { a.weight0*afact, a.weight1*afact, a.weight2*afact, a.weight3*afact,
                                    b.weight0*fact, b.weight1*fact, b.weight2*fact, b.weight3};

                for (int i = 0; i < 8; ++i)
                {
                    if (resultWeightForBone.ContainsKey(indexes[i]))
                    {
                        resultWeightForBone[indexes[i]] += weights[i];
                    }
                    else
                    {
                        resultWeightForBone.Add(indexes[i], weights[i]);
                    }
                }
            }

            // Sort by weight
            List<KeyValuePair<int, float>> mList = new List<KeyValuePair<int, float>>(resultWeightForBone);

            mList.Sort((x, y) => y.Value.CompareTo(x.Value));
            // Make sure there are at least 4 entries
            while (mList.Count < 4) mList.Add(new KeyValuePair<int, float>(0, 0.0f));

            float weightSum = mList[0].Value + mList[1].Value + mList[2].Value + mList[3].Value;
            float weightFact = 1.0f;
            if (weightSum != 0.0f) weightFact = 1.0f / weightSum;

            BoneWeight result = new BoneWeight();
            result.boneIndex0 = mList[0].Key;
            result.weight0 = mList[0].Value * weightFact;
            result.boneIndex1 = mList[1].Key;
            result.weight1 = mList[1].Value * weightFact;
            result.boneIndex2 = mList[2].Key;
            result.weight2 = mList[2].Value * weightFact;
            result.boneIndex3 = mList[3].Key;
            result.weight3 = mList[3].Value * weightFact;

            return result;
        }

        private static BoneWeight BoneWeightBaricentricInterpolation(BoneWeight a, BoneWeight b, BoneWeight c, float facta, float factb, float factc)
        {
            if (facta >= 1.0f) return a;
            else if (factb >= 1.0f) return b;
            else if (factc >= 1.0f) return c;

            int[] indexes = {a.boneIndex0, a.boneIndex1, a.boneIndex2, a.boneIndex3,
                             b.boneIndex0, b.boneIndex1, b.boneIndex2, b.boneIndex3,
                             c.boneIndex0, c.boneIndex1, c.boneIndex2, c.boneIndex3};
            float[] weights = { a.weight0*facta, a.weight1*facta, a.weight2*facta, a.weight3*facta,
                                b.weight0*factb, b.weight1*factb, b.weight2*factb, b.weight3*factb,
                                c.weight0*factc, c.weight1*factc, c.weight2*factc, c.weight3*factc};

            Dictionary<int, float> resultWeightForBone = new Dictionary<int, float>(12);
            for (int i = 0; i < 12; ++i)
            {
                if (resultWeightForBone.ContainsKey(indexes[i]))
                {
                    resultWeightForBone[indexes[i]] += weights[i];
                }
                else
                {
                    resultWeightForBone.Add(indexes[i], weights[i]);
                }
            }

            // Sort by weight
            List<KeyValuePair<int, float>> mList = new List<KeyValuePair<int, float>>(resultWeightForBone);

            mList.Sort((x, y) => y.Value.CompareTo(x.Value));

            // Make sure there are 4 entires
            while (mList.Count < 4) mList.Add(new KeyValuePair<int, float>(0, 0.0f));

            float weightSum = mList[0].Value + mList[1].Value + mList[2].Value + mList[3].Value;
            float weightFact = 1.0f;
            if (weightSum != 0.0f) weightFact = 1.0f / weightSum;

            BoneWeight result = new BoneWeight();
            result.boneIndex0 = mList[0].Key;
            result.weight0 = mList[0].Value * weightFact;
            result.boneIndex1 = mList[1].Key;
            result.weight1 = mList[1].Value * weightFact;
            result.boneIndex2 = mList[2].Key;
            result.weight2 = mList[2].Value * weightFact;
            result.boneIndex3 = mList[3].Key;
            result.weight3 = mList[3].Value * weightFact;

            return result;
        }

        // squared difference of two boneweights 0...4
        private static float BoneWeightDeltaSqr(BoneWeight a, BoneWeight b)
        {
            int[] indexes = {a.boneIndex0, a.boneIndex1, a.boneIndex2, a.boneIndex3,
                             b.boneIndex0, b.boneIndex1, b.boneIndex2, b.boneIndex3};
            float[] weights = { a.weight0, a.weight1, a.weight2, a.weight3,
                                -b.weight0,  -b.weight1,  -b.weight2,  -b.weight3};

            Dictionary<int, float> resultWeightForBone = new Dictionary<int, float>(8);
            for (int i = 0; i < 8; ++i)
            {
                if (resultWeightForBone.ContainsKey(indexes[i]))
                {
                    resultWeightForBone[indexes[i]] += weights[i];
                }
                else
                {
                    resultWeightForBone.Add(indexes[i], weights[i]);
                }
            }

            float result = 0.0f;
            foreach (KeyValuePair<int, float> pair in resultWeightForBone)
            {
                result += pair.Value * pair.Value;
            }
            return result;
        }

        private static void NormalizeSmallVector(ref Vector3 v)
        {
            float mag = v.magnitude;
            if (mag != 0.0f) v *= 1.0f / mag;
            else v = Vector3.zero;
        }

        private static Vector3 BaricentricProjection(Vector3 point, Vector3 tr0, Vector3 tr1, Vector3 tr2)
        {
            Vector3 q = tr0;
            Vector3 u = tr1 - tr0;
            Vector3 v = tr2 - tr0;
            Vector3 n = Vector3.Cross(u, v);
            float sn = n.sqrMagnitude;

            if (sn == 0.0f) return new Vector3(0.333333f, 0.333333f, 0.333333f);

            float oneOverA4Squared = 1.0f / sn;
            Vector3 w = point - q;
            Vector3 res = new Vector3();
            res[2] = Vector3.Dot(Vector3.Cross(u, w), n) * oneOverA4Squared;
            res[1] = Vector3.Dot(Vector3.Cross(w, v), n) * oneOverA4Squared;
            res[0] = 1.0f - res[1] - res[2];
            return res;
        }

        private static bool AreBaricentricCoordsInsideTriangle(Vector3 bari)
        {
            if (bari.x < 0.0f || bari.x > 1.0f) return false;
            if (bari.y < 0.0f || bari.y > 1.0f) return false;
            if (bari.z < 0.0f || bari.z > 1.0f) return false;
            else return true;
        }

        // returns 1 for equilateral and 0 for triangle with a 180 degree angle
        // this is a squared metric for performance
        private static float TriangleCompactnessSqr(Vector3[] triangle)
        {
            Vector3 e0 = triangle[1] - triangle[0];
            Vector3 e1 = triangle[2] - triangle[1];
            Vector3 e2 = triangle[0] - triangle[2];
            float div = e0.sqrMagnitude + e1.sqrMagnitude + e2.sqrMagnitude;
            if (div == 0.0f) return 0.0f;
            float val = 12.0f * Vector3.Cross(e0, e1).sqrMagnitude; // 4*sqrt(3)*triangle area SQUARED
            return val / (div * div);
        }

        // Return the index of the axis with the largest absolute value (0 = x 1 = y 2 = z)
        private static int AxisWithLargestMagniture(Vector3 vec)
        {
            float v0 = Mathf.Abs(vec.x);
            float v1 = Mathf.Abs(vec.y);
            float v2 = Mathf.Abs(vec.z);
            if (v0 > v1) if (v0 > v2) return 0;
                else if (v1 > v2) return 1;
            return 2;
        }

        private static int Vector3CompareWithTolerance(Vector3 a, Vector3 b, float tolerance)
        {
            if (a.x < b.x - tolerance) return -1;
            if (a.x > b.x + tolerance) return 1;
            if (a.y < b.y - tolerance) return -1;
            if (a.y > b.y + tolerance) return 1;
            if (a.z < b.z - tolerance) return -1;
            if (a.z > b.z + tolerance) return 1;
            return 0;
        }

        private static int Vector3Compare(Vector3 a, Vector3 b)
        {
            if (a.x < b.x) return -1;
            if (a.x > b.x) return 1;
            if (a.y < b.y) return -1;
            if (a.y > b.y) return 1;
            if (a.z < b.z) return -1;
            if (a.z > b.z) return 1;
            return 0;
        }

        private static int Vector2CompareWithTolerance(Vector2 a, Vector2 b, float tolerance)
        {
            if (a.x < b.x - tolerance) return -1;
            if (a.x > b.x + tolerance) return 1;
            if (a.y < b.y - tolerance) return -1;
            if (a.y > b.y + tolerance) return 1;
            return 0;
        }

        private static int Vector2Compare(Vector2 a, Vector2 b)
        {
            if (a.x < b.x) return -1;
            if (a.x > b.x) return 1;
            if (a.y < b.y) return -1;
            if (a.y > b.y) return 1;
            return 0;
        }

        private static int BoneWeightCompare(BoneWeight bwa, BoneWeight bwb)
        {
            if (bwa.boneIndex0 < bwb.boneIndex0) return -1;
            if (bwa.boneIndex0 > bwb.boneIndex0) return 1;
            if (bwa.weight0 < bwb.weight0) return -1;
            if (bwa.weight0 > bwb.weight0) return 1;
            if (bwa.boneIndex1 < bwb.boneIndex1) return -1;
            if (bwa.boneIndex1 > bwb.boneIndex1) return 1;
            if (bwa.weight1 < bwb.weight1) return -1;
            if (bwa.weight1 > bwb.weight1) return 1;
            if (bwa.boneIndex2 < bwb.boneIndex2) return -1;
            if (bwa.boneIndex2 > bwb.boneIndex2) return 1;
            if (bwa.weight2 < bwb.weight2) return -1;
            if (bwa.weight2 > bwb.weight2) return 1;
            if (bwa.boneIndex3 < bwb.boneIndex3) return -1;
            if (bwa.boneIndex3 > bwb.boneIndex3) return 1;
            if (bwa.weight3 < bwb.weight3) return -1;
            if (bwa.weight3 > bwb.weight3) return 1;
            return 0;
        }

        private static float ProjectedRatioOfPointOnVector(Vector3 pt, Vector3 v0, Vector3 v1)
        {
            Vector3 vec = v1 - v0;
            float fact = vec.magnitude;
            if (fact == 0.0f) return 0.0f;
            fact = 1.0f / fact;
            vec *= fact;
            float dist = Vector3.Dot(pt - v0, vec);
            return Mathf.Clamp01(dist * fact);
        }

        // Weighted squared distance between colors. 0....1
        private static float ColorDeltaSqr(Color a, Color b)
        {
            float dr = (a.r - b.r) * 0.30f;
            float dg = (a.g - b.g) * 0.59f;
            float db = (a.b - b.b) * 0.11f;

            float result = dr * dr + dg * dg + db * db;
            return result * 2.22123500666371f;
        }


        private class Vertex
        {
            public Vector3 coords;
            public int mark = 0;
            public bool valid = true;
            public IndexList linkedFaces = new IndexList(18);
            public Vector3 normal;
            public Color color;
            public BoneWeight boneWeight;

            public Vertex()
            {
                coords = Vector3.zero;
            }

            public Vertex(Vector3 v)
            {
                coords = v;
            }

            public Vertex(float x, float y, float z)
            {
                coords = new Vector3(x, y, z);
            }
        }

        private class VertexPair
        {
            public int[] v = new int[2];

            public VertexPair(int a, int b)
            {
                v[0] = a;
                v[1] = b;
            }

            public int OtherVertex(int vertexIndex)
            {
                if (vertexIndex == v[0]) return v[1];
                else return v[0];
                /*else if (v == v2) return v1;
                else Debug.LogError("Invalid Edge");
                return -1;*/
            }

            public void ReplaceVertex(int vertexIndexOld, int vertexIndexNew)
            {
                if (v[0] == vertexIndexOld) v[0] = vertexIndexNew;
                if (v[1] == vertexIndexOld) v[1] = vertexIndexNew;
                //		if (v1 == v2) {	this means the pair is invalid!			
                //		}
            }

            public void Invalidate(int vertexIndex = 0)
            {
                v[0] = vertexIndex;
                v[1] = vertexIndex;
            }

            public bool ContainsVertex(int vertexIndex)
            {
                if (v[0] == vertexIndex) return true;
                return v[1] == vertexIndex;
            }
        }

        private class Edge : VertexPair
        {
            public float crease;
            public float angle;
            public IndexList linkedFaces = new IndexList(18);
            public int mark;
            public Edge(int a, int b) : base(a, b)
            {
            }
        }

        private class Face
        {
            public int[] v = new int[4];
            public int mark = 0;
            public bool valid = true;
            public Vector3 normal;
            public Vector3[] vertexNormal = new Vector3[4];
            public int material;
            public Vector2[] uv1 = new Vector2[4];
            public Vector2[] uv2 = new Vector2[4];
            public int cornerCount = 3;

            public Face(int numVerts)
            {
                cornerCount = numVerts;
            }

            public Face(int a, int b, int c)
            {
                v[0] = a;
                v[1] = b;
                v[2] = c;
                cornerCount = 3;
            }

            public Face(int a, int b, int c, int d)
            {
                v[0] = a;
                v[1] = b;
                v[2] = c;
                v[3] = d;
                cornerCount = 4;
            }

            // one less comparison, but only for triangles
            public int CornerIndexTriangle(int vertexIndex)
            {
                if (v[0] == vertexIndex) return 0;
                if (v[1] == vertexIndex) return 1;
                return 2;
            }

            public int CornerIndex(int vertexIndex)
            {
                // new unsafe but faster way
                if (v[0] == vertexIndex) return 0;
                if (v[1] == vertexIndex) return 1;
                if (cornerCount == 3)
                { // can only be 3 or 4
                    return 2;
                }
                else
                {
                    if (v[2] == vertexIndex) return 2;
                    return 3;
                }


            }

            public Vector3 VertexNormalForVertexIndex(int vertexIndex)
            {
                for (int i = 0; i < cornerCount; ++i)
                {
                    if (v[i] == vertexIndex) return vertexNormal[i];
                }

                return Vector3.zero;
            }

            public int ReplaceVertex(int a, int b)
            {
                int count = 0;
                for (int i = 0; i < cornerCount; ++i)
                {
                    if (v[i] == a)
                    {
                        v[i] = b;
                        count++;
                    }
                }
                return count;
            }

            public void CopyVertexInfoFromFace(Face sFace, int sCorner, int tCorner)
            {
                v[tCorner] = sFace.v[sCorner];
                vertexNormal[tCorner] = sFace.vertexNormal[sCorner];
                uv1[tCorner] = sFace.uv1[sCorner];
                uv2[tCorner] = sFace.uv2[sCorner];
            }

            public bool VerticesInOrder(int a, int b)
            {
                int i;
                for (i = 0; i < cornerCount - 1; ++i)
                {
                    if (a == v[i]) return (b == v[i + 1]);
                }
                if (a == v[i]) return (b == v[0]);
                else Debug.LogError("isInOrder? first vertex index not found");
                return false;
            }
        }

        private class CollapseInfo
        {
            public VertexPair vp;
            public Vector3 targetPosition;
            public float positionCost = -1.0f; // Cost of just moving to targetPosition
            public float cost; // Total cose including penalties

            public float Ratio(BaseMesh m)
            {
                return ProjectedRatioOfPointOnVector(targetPosition, m.vertices[vp.v[0]].coords, m.vertices[vp.v[1]].coords);
            }
        }

        private class IndexList
        {
            public int[] array;
            int _capacity;
            public int Count;

            public IndexList(int capacity)
            {
                _capacity = capacity;
                array = new int[_capacity];
            }

            public void Add(int val)
            {
                if (Count >= _capacity)
                {
                    GrowToCapacity(_capacity + _capacity);
                }
                array[Count++] = val;
            }

            public void AddUnsafe(int val)
            {
                array[Count++] = val;
            }

            public void Clear()
            {
                Count = 0;
            }

            public void GrowToCapacity(int newCapacity)
            {
                if (newCapacity > _capacity)
                {
                    System.Array.Resize<int>(ref array, newCapacity);
                    _capacity = newCapacity;
                }
            }

            public void Remove(int val)
            {
                for (int i = 0; i < Count; ++i)
                {
                    if (array[i] == val)
                    {
                        System.Array.Copy(array, i + 1, array, i, Count - i - 1);
                        Count--;
                        return;
                    }
                }
            }

            public int this[int key]
            {
                get
                {
                    return array[key];
                }
                set
                {
                    array[key] = value;
                }
            }

        }

        private delegate void ProgressDelegate(string text, float val);


        [System.Serializable]
        private class SimplifyParameters
        {

            public int edgesToCollapse = -1;


            public int targetFaceCount = -1;

            public float maximumError = -1;


            public bool recalculateVertexPositions = true;


            public bool preventNonManifoldEdges = false;


            public float borderWeight = 1.0f;


            public float materialSeamWeight = 1.0f;


            public float uvSeamWeight = 1.0f;


            public float uv2SeamWeight = 0.0f;


            public float creaseWeight = 0.0f;


            public float boneWeightProtection = 0.1f;


            public float vertexColorProtection = 0.0f;


            public int maxEdgesPerVertex = 18;


            public bool checkTopology = true;

        }


        private class Simplify
        {
            SimplifyParameters _parameters;

            public ProgressDelegate progressDelegate = null;

            const bool localizeErrors = true;


            const float kMeshPenaltyNonManifold = 1e7f;
            const float kMeshPenaltyBadTopology = 1e6f;
            const float kMeshPenaltyMaxEdgesPerVertex = 1e5f;

            MinHeap<CollapseInfo> heap = null;
            HeapNode<CollapseInfo>[] heapNodes = null;
            PlaneDistanceError[] pdePerVertex = null;
            MeshEdges mesh = null;

            const int kProgressGroups = 100;
            const float kProgressInterval = 0.2f;

            float meshSize = 1.0f;
            bool noPenalties = false;
            private static void Triangulate(BaseMesh mesh)
            {
                if (mesh.topology == MeshTopology.Triangles) return;

                int faceIndex, cornerIndex;

                int numFaces = mesh.faceCount();
                Vector3[] coords = new Vector3[4];
                for (faceIndex = 0; faceIndex < numFaces; ++faceIndex)
                {
                    Face f = mesh.faces[faceIndex];
                    if (f.valid && f.cornerCount == 4)
                    {
                        Face fnew = new Face(3);

                        fnew.material = f.material;
                        for (cornerIndex = 0; cornerIndex < 4; ++cornerIndex)
                        {
                            coords[cornerIndex] = mesh.vertices[f.v[cornerIndex]].coords;
                        }

                        Vector3 diag02 = coords[0] - coords[2];
                        Vector3 diag13 = coords[1] - coords[3];
                        if (diag02.sqrMagnitude < diag13.sqrMagnitude)
                        {

                            fnew.CopyVertexInfoFromFace(f, 0, 0);
                            fnew.CopyVertexInfoFromFace(f, 2, 1);
                            fnew.CopyVertexInfoFromFace(f, 3, 2);
                        }
                        else
                        {

                            fnew.CopyVertexInfoFromFace(f, 3, 0);
                            fnew.CopyVertexInfoFromFace(f, 1, 1);
                            fnew.CopyVertexInfoFromFace(f, 2, 2);
                            f.CopyVertexInfoFromFace(f, 3, 2);
                        }
                        mesh.AddFace(fnew);

                        f.cornerCount = 3;
                    }
                }

                int numVerts = mesh.vertCount();
                for (int vertIndex = 0; vertIndex < numVerts; ++vertIndex)
                {
                    mesh.vertices[vertIndex].linkedFaces = new IndexList(18);
                }
                numFaces = mesh.faceCount();

                for (faceIndex = 0; faceIndex < numFaces; ++faceIndex)
                {
                    Face f = mesh.faces[faceIndex];
                    for (cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                    {
                        mesh.vertices[f.v[cornerIndex]].linkedFaces.Add(faceIndex);
                    }
                }
                mesh.topology = MeshTopology.Triangles;
            }
            private static void TriangulateWithEdges(MeshEdges mesh)
            {
                if (mesh.topology == MeshTopology.Triangles) return;

                int oldNumFaces = mesh.faceCount();
                Triangulate((BaseMesh)mesh);
                int newNumFaces = mesh.faceCount();
                for (int faceIndex = oldNumFaces; faceIndex < newNumFaces; ++faceIndex)
                {
                    Face f = mesh.faces[faceIndex];
                    Edge e = new Edge(f.v[0], f.v[1]);
                    mesh.AddEdge(e);
                }
                mesh.RegenerateVertexLinkedEdges();
                mesh.GenerateEdgeTopology();

            }
            public void Execute(ref MeshEdges mesh, SimplifyParameters parameters)
            {
                if (mesh.vertCount() == 0) return;

                if (mesh.topology != MeshTopology.Triangles)
                {
                    TriangulateWithEdges(mesh);
                }

                if (progressDelegate != null) progressDelegate("Initialize", 0.0f);
                InitializeCollapsing(mesh, parameters);
                if (parameters.edgesToCollapse > 0)
                {
                    Collapse(parameters.edgesToCollapse);
                }
                else if (parameters.targetFaceCount > 0)
                {
                    int totalFacesToRemove = mesh.numValidFaces - parameters.targetFaceCount;
                    int progressCounter = 0;
                    float t = Time.realtimeSinceStartup + kProgressInterval;
                    while (mesh.numValidFaces > parameters.targetFaceCount)
                    {
                        Collapse(1);
                        progressCounter--;
                        if (progressCounter <= 0)
                        {
                            progressCounter = kProgressGroups;
                            if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null)
                            {
                                t = Time.realtimeSinceStartup;
                                int facesRemoved = totalFacesToRemove - (mesh.numValidFaces - parameters.targetFaceCount);
                                progressDelegate("Mesh Faces " + mesh.numValidFaces + "->" + parameters.targetFaceCount, 0.1f + 0.9f * ((float)facesRemoved) / ((float)totalFacesToRemove));
                            }
                        }
                    }
                }
                else if (parameters.maximumError > 0)
                {
                    meshSize = _determineMeshSize(mesh);
                    int totalFacesToRemove = mesh.numValidFaces;
                    float t = Time.realtimeSinceStartup + kProgressInterval;
                    float costThreshold = meshSize * parameters.maximumError * 0.001f;
                    costThreshold *= costThreshold;
                    int progressCounter = 0;
                    while (true)
                    {
                        int num = Collapse(1, costThreshold);
                        if (num == 0) break;
                        progressCounter--;
                        if (progressCounter <= 0)
                        {
                            progressCounter = kProgressGroups;
                            if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null)
                            {
                                t = Time.realtimeSinceStartup;
                                int facesRemoved = totalFacesToRemove - (mesh.numValidFaces - parameters.targetFaceCount);
                                progressDelegate("Mesh Faces " + mesh.numValidFaces + "->" + parameters.targetFaceCount, 0.1f + 0.9f * ((float)facesRemoved) / ((float)totalFacesToRemove)); // TODO: do this better
                            }
                        }
                    }
                }
                mesh.RebuildMesh();
                Cleanup();
            }
            private void InitializeCollapsing(MeshEdges _mesh, SimplifyParameters parameters)
            {
                mesh = _mesh;
                _parameters = parameters;
                if (_mesh.hasBoneWeights == false) parameters.boneWeightProtection = 0.0f;
                if (_mesh.hasVertexColors == false) parameters.vertexColorProtection = 0.0f;
                _calculatePdePerVertex();
                int numEdges = mesh.edgeCount();
                heapNodes = new HeapNode<CollapseInfo>[numEdges];
                heap = new MinHeap<CollapseInfo>();
                int progressCounter = kProgressGroups;
                float t = Time.realtimeSinceStartup;
                for (int i = 0; i < numEdges; ++i)
                {
                    CollapseInfo pc = new CollapseInfo();
                    _calculateEdgeCost(i, pc);

                    
                    heapNodes[i] = heap.Insert(new HeapNode<CollapseInfo>(pc.cost, pc));
                    progressCounter--;
                    if (progressCounter <= 0)
                    {
                        progressCounter = kProgressGroups;
                        if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null)
                        {
                            t = Time.realtimeSinceStartup;
                            progressDelegate("Initialize Edge " + i + "/" + numEdges, 0.1f * ((float)i) / ((float)numEdges));
                        }
                    }
                }
                noPenalties = (parameters.checkTopology == false
                    && parameters.maxEdgesPerVertex == 0
                    && parameters.preventNonManifoldEdges == false
                    && parameters.boneWeightProtection <= 0.0f
                    && parameters.vertexColorProtection <= 0.0f);
            }

            public void Cleanup()
            {
                _parameters = null;
                progressDelegate = null;
                pdePerVertex = null;
                heapNodes = null;
                heap = null;
                mesh = null;
            }

            List<int> _edgesToUpdate = new List<int>(32);
            List<int> _surroundingVerts = new List<int>(32);

            public int Collapse(int numEdgesToCollapse = 1, float maxCost = 1e6f)
            {
                int collapsesDone = 0;
                while (numEdgesToCollapse > 0)
                {
                    HeapNode<CollapseInfo> node;
                    do
                    {
                        node = heap.Extract();
                    } while (node != null && mesh.isVertexPairValid(node.obj.vp) == false);
                    if (node == null) break;

                    if (node.heapValue > maxCost) break;

                    CollapseInfo cinfo = node.obj;
                    VertexPair vp = cinfo.vp;
                    pdePerVertex[vp.v[0]].OpAdd(pdePerVertex[vp.v[1]]);

                    int vindex = mesh.CollapseVertexPair(cinfo);
                    _edgesToUpdate.Clear();
                    _surroundingVerts.Clear();

                    mesh.CollectVerticesAroundVertex(vindex, ref _surroundingVerts);

                    // TODO: do this better -> why?
                    int mark = mesh.GetUniqueTag();
                    for (int i = 0; i < _surroundingVerts.Count; ++i)
                    {
                        List<int> lEdges = mesh.linkedEdgesForVert(_surroundingVerts[i]);
                        for (int j = 0; j < lEdges.Count; ++j)
                        {
                            int edgeIndex = lEdges[j];
                            Edge e = mesh.edges[edgeIndex];
                            if (e.mark != mark)
                            {
                                e.mark = mark;
                                if (mesh.IsEdgeValid(edgeIndex))
                                {
                                    _edgesToUpdate.Add(edgeIndex);
                                }
                            }
                        }
                    }

                    // DO the update
                    for (int i = 0; i < _edgesToUpdate.Count; ++i)
                    {
                        int edgeIndex = _edgesToUpdate[i];
                        HeapNode<CollapseInfo> hnode = heapNodes[edgeIndex];
                        if (mesh.edges[edgeIndex].ContainsVertex(vindex))
                        {
                            _calculateEdgeCost(edgeIndex, hnode.obj);
                        }
                        else
                        {
                            _updateEdgePenalties(edgeIndex, hnode.obj, vindex);
                        }
                        heap.Update(hnode, hnode.obj.cost);
                    }

                    numEdgesToCollapse--;
                    collapsesDone++;
                }
                return collapsesDone;
            }

            PlaneDistanceError pde = new PlaneDistanceError();

            void _recalculateVertexPde(int vertexIndex)
            {
                IndexList faceIndexes = mesh.vertices[vertexIndex].linkedFaces;
                pdePerVertex[vertexIndex].Clear();
                for (int i = 0; i < faceIndexes.Count; ++i)
                {
                    Face f = mesh.faces[faceIndexes[i]];
                    if (f.valid)
                    {
                        _determinePdeForFace(faceIndexes[i], ref pde);
                        pdePerVertex[vertexIndex].OpAdd(pde);
                    }
                }
            }

            void _calculatePdePerVertex()
            {
                // This constructs a error term for every vertex based on the surrounding faces
                // The value of this term is a sum of the squared distances to all the planes
                if (mesh == null)
                {
                    Debug.LogError("Mesh is not initialized. Could not calculate plane distance errors."); return;
                }

                int numVerts = mesh.vertCount();
                int numFaces = mesh.faceCount();
                // Initialize pdes
                pdePerVertex = new PlaneDistanceError[numVerts];
                for (int i = 0; i < numVerts; ++i) pdePerVertex[i] = new PlaneDistanceError();

                PlaneDistanceError pde = new PlaneDistanceError();
                for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex)
                {
                    _determinePdeForFace(faceIndex, ref pde);
                    // Add to all point errors of the face
                    Face face = mesh.faces[faceIndex];
                    for (int i = 0; i < face.cornerCount; ++i)
                    {
                        pdePerVertex[face.v[i]].OpAdd(pde);
                    }
                }

                // Mesh constrains
                int numEdges = mesh.edgeCount();
                // Look for border vertices and mesh discontinuities 
                for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex)
                {
                    if (_determineEdgeContraintForEdge(edgeIndex, ref pde))
                    {
                        Edge edge = mesh.edges[edgeIndex];
                        pdePerVertex[edge.v[0]].OpAdd(pde);
                        pdePerVertex[edge.v[1]].OpAdd(pde);
                    }
                }
            }

            void _determinePdeForFace(int faceIndex, ref PlaneDistanceError pde)
            {
                Face face = mesh.faces[faceIndex];
                Vector3 n = mesh.CalculateFaceNormal(faceIndex);

                // Create error struct from the plane of the face (using face normal)
                float offset = -Vector3.Dot(n, mesh.vertices[face.v[0]].coords);
                pde.Set(n.x, n.y, n.z, offset, mesh.CalculateFaceArea(faceIndex));
                // Multiply by face area (factor) for weighting
                pde.OpMul(pde.Factor());
            }

            bool _determineEdgeContraintForEdge(int edgeIndex, ref PlaneDistanceError pde)
            {
                float weight = 0.0f;
                if (mesh.IsEdgeBorder(edgeIndex))
                {
                    weight = _parameters.borderWeight;
                }
                else
                {
                    if (mesh.edges[edgeIndex].crease == 1.0f) weight = Mathf.Max(weight, _parameters.creaseWeight);
                    if (mesh.numMaterials > 1 && mesh.isEdgeMaterialSeam(edgeIndex)) weight = Mathf.Max(weight, _parameters.materialSeamWeight);
                    if (mesh.hasUV1 && mesh.IsEdgeUV1Seam(edgeIndex)) weight = Mathf.Max(weight, _parameters.uvSeamWeight);
                    if (mesh.hasUV2 && mesh.IsEdgeUV2Seam(edgeIndex)) weight = Mathf.Max(weight, _parameters.uv2SeamWeight);
                }
                if (weight > 0.0f)
                {
                    _determinePdeToConstrainEdge(mesh.edges[edgeIndex], weight, ref pde);
                    return true;
                }
                return false;
            }

            PlaneDistanceError pde2 = new PlaneDistanceError();

            void _determinePdeToConstrainEdge(Edge edge, float weight, ref PlaneDistanceError pde)
            {
                int vi0 = edge.v[0];
                int vi1 = edge.v[1];

                Vector3 v0 = mesh.vertices[vi0].coords;
                Vector3 vEdge = mesh.vertices[vi1].coords - v0;
                Vector3 edgeNormal;
                pde.Clear();
                float mag = vEdge.sqrMagnitude;
                for (int i = 0; i < edge.linkedFaces.Count; ++i)
                {
                    edgeNormal = mesh.faces[edge.linkedFaces[i]].normal;

                    Vector3 n = Vector3.Cross(vEdge, edgeNormal); // normal to edge and face
                    NormalizeSmallVector(ref n);

                    float d = -Vector3.Dot(n, v0);
                    pde2.Set(n.x, n.y, n.z, d, mag);
                    // Multiply by face area (factor) for weighting
                    pde2.OpMul(pde2.Factor() * weight * 0.5f);
                    pde.OpAdd(pde2);
                }
            }

            IndexList faces0 = new IndexList(32);
            IndexList faces1 = new IndexList(32);
            IndexList commonFaces = new IndexList(32);

            void _calculateEdgeCost(int edgeIndex, CollapseInfo cinfo)
            {
                Edge edge = mesh.edges[edgeIndex];
                int vindex0 = edge.v[0];
                int vindex1 = edge.v[1];
                Vertex v0 = mesh.vertices[vindex0];
                Vertex v1 = mesh.vertices[vindex1];

                cinfo.vp = edge;

                PlaneDistanceError pde = pdePerVertex[edge.v[0]] + pdePerVertex[edge.v[1]];

                if (_parameters.recalculateVertexPositions)
                {
                    if (mesh.IsEdgeBorder(edgeIndex) == false && pde.OptimalVertex(ref cinfo.targetPosition))
                    {
                        cinfo.cost = (float)pde.CalculateError(cinfo.targetPosition);
                        //	Debug.Log(">optimal placement");
                    }
                    else if (pde.OptimalVertexLinear(ref cinfo.targetPosition, v0.coords, v1.coords))
                    {
                        // the error term is not solvable
                        // Try to find a vert on the line going from v0 to v1
                        cinfo.cost = (float)pde.CalculateError(cinfo.targetPosition);
                        //	Debug.Log(">line placement");
                    }
                    else
                    {
                        // Choose vert from the two endpoints and the midpoint
                        Vector3 tp = 0.5f * (v0.coords + v1.coords);
                        double error0 = pde.CalculateError(v0.coords);
                        double error1 = pde.CalculateError(v1.coords);
                        double error2 = pde.CalculateError(tp);
                        if (error0 < error1)
                        {
                            if (error0 < error2)
                            {
                                cinfo.targetPosition = v0.coords; cinfo.cost = (float)error0;
                            }
                            else
                            {
                                cinfo.targetPosition = tp; cinfo.cost = (float)error2;
                            }
                        }
                        else
                        {
                            if (error1 < error2)
                            {
                                cinfo.targetPosition = v1.coords; cinfo.cost = (float)error1;
                            }
                            else
                            {
                                cinfo.targetPosition = tp; cinfo.cost = (float)error2;
                            }
                        }
                    }
                }
                else
                {
                    double error0 = pde.CalculateError(v0.coords);
                    double error1 = pde.CalculateError(v1.coords);
                    if (error0 < error1)
                    {
                        cinfo.targetPosition = v0.coords;
                        cinfo.cost = (float)error0;
                    }
                    else
                    {
                        cinfo.targetPosition = v1.coords;
                        cinfo.cost = (float)error1;
                    }
                }

                // Choose minimal error point -> bad for border edges which are underdefined
                if (localizeErrors) cinfo.cost *= 1.0f / ((float)pde.Factor());
         
                if (null != MeshLodHelper.isAlwayKeep)
                {
                    if (MeshLodHelper.isAlwayKeep(vindex0))
                    {
                        cinfo.cost = float.MaxValue;
                    }
                    if (MeshLodHelper.isAlwayKeep(vindex1))
                    {

                        cinfo.cost = float.MaxValue;
                    }
                }

                cinfo.positionCost = cinfo.cost;

                if (noPenalties == false)
                {
                    _updateEdgePenalties(edgeIndex, cinfo, -1);
                }
            }

            void _updateEdgePenalties(int edgeIndex, CollapseInfo cinfo, int movedVertexIndex = -1)
            {
                Edge edge = mesh.edges[edgeIndex];
                int vindex0 = edge.v[0];
                int vindex1 = edge.v[1];
                Vertex v0 = mesh.vertices[vindex0];
                Vertex v1 = mesh.vertices[vindex1];

                faces0.Clear();
                faces1.Clear();
                commonFaces.Clear();

                bool hadPenalty = (cinfo.cost >= kMeshPenaltyMaxEdgesPerVertex);

                cinfo.cost = cinfo.positionCost; // reset cost
                                                 // determine the faces involved in the collapse .. 
                mesh.CollectCollapseFacesForVertexPair(mesh.edges[edgeIndex], faces0, faces1, commonFaces);

                // Penalties

                int filterTag = 0;

                if (_parameters.preventNonManifoldEdges && _producesNonManifold(mesh, cinfo))
                {
                    cinfo.cost += kMeshPenaltyNonManifold; // largest penalty first
                }
                else
                {
                    if (movedVertexIndex != -1 && hadPenalty == false)
                    {
                        // For cinfos that are not new and had no penalty before, all faces besides the onces connected
                        // to the moved vertex can be skipped.
                        filterTag = mesh.GetUniqueTag();
                        IndexList linkedFaces = mesh.vertices[movedVertexIndex].linkedFaces;
                        int count = linkedFaces.Count;
                        for (int i = 0; i < count; ++i) mesh.faces[linkedFaces[i]].mark = filterTag;
                    }
                    if (_parameters.checkTopology && _producesBadTopology(mesh, cinfo, filterTag))
                    {
                        // Apply penalties for bad collapses
                        cinfo.cost += kMeshPenaltyBadTopology;
                    }
                    else if (_parameters.maxEdgesPerVertex > 0 && _vertexDegreeAfterCollapse(mesh, cinfo) > _parameters.maxEdgesPerVertex)
                    { // Hard coded at 18 for now.. rarely reached, but always check!
                      // Avoid collapses leading to excessive stars (many verts connected to one)
                        cinfo.cost += kMeshPenaltyMaxEdgesPerVertex;
                    }
                }
               

                // Additional penalties:			
                float val = 0.0f;
                if (_parameters.boneWeightProtection > 0.0f)
                {
                    val += BoneWeightDeltaSqr(v0.boneWeight, v1.boneWeight) * _parameters.boneWeightProtection;
                }
                if (_parameters.vertexColorProtection > 0.0f)
                {
                    val += ColorDeltaSqr(v0.color, v1.color) * _parameters.vertexColorProtection;
                }
                if (val != 0.0f)
                {
                    cinfo.cost += 0.1f * val * mesh.EdgeLengthSqr(edgeIndex);
                }
            }

            // Count number of surrounding faces after the collapse
            int _vertexDegreeAfterCollapse(BaseMesh mesh, CollapseInfo ci)
            {
                return faces0.Count + faces1.Count;
            }

            bool _producesNonManifold(BaseMesh mesh, CollapseInfo ci)
            {
                // ... If v0 and v1 don't have nr of commonfaces common neighbours
                List<Vertex> vertices = mesh.vertices;
                List<Face> faces = mesh.faces;
                int common = commonFaces.Count;
                int tag = mesh.GetUniqueTag();

                int count = faces0.Count;
                int[] faceArray = faces0.array;
                for (int i = 0; i < count; ++i)
                {
                    int[] v = faces[faceArray[i]].v;
                    // known to be a valid triangle
                    vertices[v[0]].mark = tag;
                    vertices[v[1]].mark = tag;
                    vertices[v[2]].mark = tag;
                }
                count = faces1.Count;
                faceArray = faces1.array;
                for (int i = 0; i < count; ++i)
                {
                    int[] v = faces[faceArray[i]].v;
                    if (vertices[v[0]].mark == tag) { common--; if (common < 0) return true; vertices[v[0]].mark = 0; }
                    if (vertices[v[1]].mark == tag) { common--; if (common < 0) return true; vertices[v[1]].mark = 0; }
                    if (vertices[v[2]].mark == tag) { common--; if (common < 0) return true; vertices[v[2]].mark = 0; }
                }

                return common != 0;
            }

            //Vector3[] tr = new Vector3[3];

            /*
            // rchecks result triangles against limit
            bool _producesSharpTriangles(Mesh mesh, CollapseInfo ci, float limitSqr) {
                for (int k = 0; k < 2; ++k) {
                    int vindex = ci.vp.v[k];
                    List<int> linkedFaces = (k == 0) ? faces0 : faces1;
                    for (int i = 0; i < linkedFaces.Count; ++i) {
                        Face f = mesh.faces[linkedFaces[i]];					
                        if (f.v[0] == vindex) 		{ tr[0] = ci.targetPosition; 			tr[1] = mesh.vertex(f.v[1]).coords; tr[2] = mesh.vertex(f.v[2]).coords; }
                        else if (f.v[1] == vindex) 	{ tr[0] = mesh.vertex(f.v[0]).coords; 	tr[1] = ci.targetPosition; 			tr[2] = mesh.vertex(f.v[2]).coords; }
                        else 						{ tr[0] = mesh.vertex(f.v[0]).coords; 	tr[1] = mesh.vertex(f.v[1]).coords;	tr[2] = ci.targetPosition; 			}
                        float val = TriangleCompactnessSqr(tr);
                        if (val < limitSqr) return true;
                    }
                }
                return false;
            }
            */

            // Check topology to avoid flipping the mesh by collapsing
            bool _producesBadTopology(BaseMesh mesh, CollapseInfo ci, int filterTag = 0)
            {
                Vector3 p1, origPos, vOpp, faceNormal, normal;
                List<Vertex> vertices = mesh.vertices;
                List<Face> faces = mesh.faces;

                for (int k = 0; k < 2; ++k)
                {
                    int vindex = ci.vp.v[k];
                    origPos = vertices[vindex].coords;
                    IndexList linkedFacesList = (k == 0) ? faces0 : faces1;
                    int[] linkedFaces = linkedFacesList.array;
                    for (int i = 0; i < linkedFacesList.Count; ++i)
                    {
                        Face f = faces[linkedFaces[i]];
                        if (f.mark >= filterTag)
                        {
                            // Construct a plane from opposing sides (p1->p2) && the face normal -> todo make this better (faster)
                            if (f.v[0] == vindex)
                            {
                                p1 = vertices[f.v[1]].coords;
                                vOpp = vertices[f.v[2]].coords - p1;
                            }
                            else if (f.v[1] == vindex)
                            {
                                p1 = vertices[f.v[2]].coords;
                                vOpp = vertices[f.v[0]].coords - p1;
                            }
                            else
                            {
                                p1 = vertices[f.v[0]].coords;
                                vOpp = vertices[f.v[1]].coords - p1;
                            }

                            faceNormal = Vector3.Cross(vOpp, origPos - p1); // the face normal
                            normal = Vector3.Cross(faceNormal, vOpp); // normal of constructed plane
                            if (Vector3.Dot(ci.targetPosition - p1, normal) < 0.0f) return true;
                        }
                    }
                }
                return false;
            }

            /*
             * NOT USED. the badtopology test is faster & better

              bool _producesInvertedFaces(BaseMesh mesh, CollapseInfo ci) {
                Vector3[] tr = new Vector3[3];
                for (int k = 0; k < 2; ++k) {
                    int vindex = ci.vp.v[k];
                    List<int> linkedFaces = (k == 0) ? faces0 : faces1;
                    for (int i = 0; i < linkedFaces.Count; ++i) {
                        Face f = mesh.faces[linkedFaces[i]];
                        Vector3 oldN = mesh.CalculateFaceNormal(linkedFaces[i]);
                        if (f.v[0] == vindex) 		{ tr[0] = ci.targetPosition; 			tr[1] = mesh.vertex(f.v[1]).coords; tr[2] = mesh.vertex(f.v[2]).coords; }
                        else if (f.v[1] == vindex) 	{ tr[0] = mesh.vertex(f.v[0]).coords; 	tr[1] = ci.targetPosition; 			tr[2] = mesh.vertex(f.v[2]).coords; }
                        else 						{ tr[0] = mesh.vertex(f.v[0]).coords; 	tr[1] = mesh.vertex(f.v[1]).coords;	tr[2] = ci.targetPosition; 			}
                        Vector3 newN = Vector3.Cross(tr[1] - tr[0], tr[2] - tr[1]);
                        if (Vector3.Dot(oldN, newN) < 0.0f) {
                            return true;
                        }
                    }
                }
                return false;
            }*/

            float _determineMeshSize(BaseMesh mesh)
            {
                Vector3 min = mesh.vertices[0].coords;
                Vector3 max = min;
                int numVerts = mesh.vertCount();
                for (int i = 0; i < numVerts; ++i)
                {
                    Vector3 c = mesh.vertices[i].coords;
                    if (c.x < min.x) min.x = c.x;
                    else if (c.x > max.x) max.x = c.x;
                    if (c.y < min.y) min.y = c.y;
                    else if (c.y > max.y) max.y = c.y;
                    if (c.z < min.z) min.z = c.z;
                    else if (c.z > max.z) max.z = c.z;
                }
                max -= min;
                float result = max.x;
                if (max.y > meshSize) meshSize = max.y;
                if (max.z > meshSize) meshSize = max.z;
                return result;
            }
        }

        private class CreaseDetect
        {

            public static void MarkCreasesFromMaterialSeams(MeshEdges mesh, float creaseStrength = 1.0f)
            {
                int numEdges = mesh.edgeCount();
                for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex)
                {
                    if (mesh.isEdgeMaterialSeam(edgeIndex))
                    {
                        mesh.edges[edgeIndex].crease = creaseStrength;
                    }
                }
            }


            public static void MarkCreasesFromFaceNormals(MeshEdges mesh, float creaseStrength = 1.0f)
            {
                mesh.CalculateEdgeLinkedFaces();
                int count = mesh.edgeCount();
                for (int edgeIndex = 0; edgeIndex < count; ++edgeIndex)
                {
                    Edge edge = mesh.edges[edgeIndex];
                    int v1 = edge.v[0];
                    int v2 = edge.v[1];
                    if (edge.linkedFaces.Count == 2)
                    {
                        Face fa = mesh.faces[edge.linkedFaces[0]];
                        Face fb = mesh.faces[edge.linkedFaces[1]];
                        Vector3 n1a = fa.VertexNormalForVertexIndex(v1);
                        Vector3 n2a = fa.VertexNormalForVertexIndex(v2);
                        Vector3 n1b = fb.VertexNormalForVertexIndex(v1);
                        Vector3 n2b = fb.VertexNormalForVertexIndex(v2);
                        if (Vector3CompareWithTolerance(n1a, n1b, mesh.equalityTolerance) != 0
                            || Vector3CompareWithTolerance(n2a, n2b, mesh.equalityTolerance) != 0)
                        {
                            edge.crease = creaseStrength;
                        }
                    }
                }
            }


            public static void MarkCreasesFromEdgeAngles(MeshEdges mesh, float angleThreshold, float creaseStrength = 1.0f)
            {
                mesh.CalculateFaceNormals();
                int numEdges = mesh.edgeCount();
                for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex)
                {
                    if (mesh.CalculateEdgeAngle(edgeIndex) >= angleThreshold)
                    {
                        mesh.edges[edgeIndex].crease = creaseStrength;
                    }
                }
            }


            public static void ClearAllCreases(MeshEdges mesh)
            {
                int numEdges = mesh.edgeCount();
                for (int edgeIndex = 0; edgeIndex < numEdges; ++edgeIndex)
                {
                    mesh.edges[edgeIndex].crease = 0.0f;
                }
            }
        }

        private class PlaneDistanceError
        {
            double[] cf = new double[10];
            double _fact;

            public PlaneDistanceError()
            {
            }

            public PlaneDistanceError(PlaneDistanceError b)
            {
                _fact = b._fact;
                for (int i = 0; i < 10; ++i) cf[i] = b.cf[i];
            }

            public PlaneDistanceError(double a, double b, double c, double d, double fact)
            {
                Set(a, b, c, d, fact);
            }

            public void Set(double a, double b, double c, double d, double fact)
            {
                cf[0] = a * a; cf[1] = a * b; cf[2] = a * c; cf[3] = a * d;
                cf[4] = b * b; cf[5] = b * c; cf[6] = b * d;
                cf[7] = c * c; cf[8] = c * d;
                cf[9] = d * d;

                _fact = fact;
            }

            public void Clear()
            {
                _fact = 0.0;
                for (int i = 0; i < 10; ++i) cf[i] = 0.0;
            }

            public void OpAdd(PlaneDistanceError b)
            {
                _fact += b._fact;
                for (int i = 0; i < 10; ++i) cf[i] += b.cf[i];
            }

            public void OpMul(double val)
            {
                for (int i = 0; i < 10; ++i) cf[i] *= val;
            }

            public void SetFactor(double fact)
            {
                _fact = fact;
            }

            public double Factor()
            {
                return _fact;
            }

            public double CalculateError(Vector3 pt)
            {
                // Evaluate Av^2 + 2bv + c
                return pt.x * (pt.x * cf[0] + 2.0 * (pt.y * cf[1] + pt.z * cf[2] + cf[3]))
                    + pt.y * (pt.y * cf[4] + 2.0 * (pt.z * cf[5] + cf[6]))
                    + pt.z * (pt.z * cf[7] + cf[8] + cf[8])
                    + cf[9];
            }

            public bool OptimalVertex(ref Vector3 result)
            {
                double t00 = cf[4] * cf[7] - cf[5] * cf[5];
                double t01 = cf[5] * cf[2] - cf[7] * cf[1];
                double t02 = cf[1] * cf[5] - cf[2] * cf[4];

                double det = cf[0] * t00 + cf[1] * t01 + cf[2] * t02;

                if (det > -1e-12 && det < 1e-12) return false;
                det = -1.0 / det;

                //	double t10 = t01; //(cf[5]*cf[2] - cf[1]*cf[7]);
                double t11 = cf[7] * cf[0] - cf[2] * cf[2];
                double t12 = cf[2] * cf[1] - cf[0] * cf[5];
                //	double t20 = t02; // cf[1]*cf[5] - cf[4]*cf[2];
                //	double t21 = t12; // cf[2]*cf[1] - cf[5]*cf[0];
                double t22 = cf[0] * cf[4] - cf[1] * cf[1];

                result.x = (float)(det * (t00 * cf[3] + t01 * cf[6] + t02 * cf[8]));
                result.y = (float)(det * (t01 * cf[3] + t11 * cf[6] + t12 * cf[8]));
                result.z = (float)(det * (t02 * cf[3] + t12 * cf[6] + t22 * cf[8]));

                return true;
            }

            public bool OptimalVertexLinear(ref Vector3 result, Vector3 p, Vector3 q)
            {
                Vector3 v = p - q;

                double av0 = cf[0] * v.x + cf[1] * v.y + cf[2] * v.z;
                double av1 = cf[1] * v.x + cf[4] * v.y + cf[5] * v.z;
                double av2 = cf[2] * v.x + cf[5] * v.y + cf[7] * v.z;

                double denom = v.x * av0 + v.y * av1 + v.z * av2;
                if (denom > -1e-12 && denom < 1e-12) return false;
                denom = 1.0 / (denom + denom);

                double aq0 = cf[0] * q.x + cf[1] * q.y + cf[2] * q.z;
                double aq1 = cf[1] * q.x + cf[4] * q.y + cf[5] * q.z;
                double aq2 = cf[2] * q.x + cf[5] * q.y + cf[7] * q.z;

                double param = (-2.0 * (cf[3] * v.x + cf[6] * v.y + cf[8] * v.z)
                    - (v.x * aq0 + v.y * aq1 + v.z * aq2)
                    - (q.x * av0 + q.y * av1 + q.z * av2)) * denom;
                result = q + Mathf.Clamp01((float)param) * v;
                return true;
            }

            public static PlaneDistanceError operator +(PlaneDistanceError a, PlaneDistanceError b)
            {
                PlaneDistanceError res = new PlaneDistanceError(a);
                res.OpAdd(b);
                return res;
            }
        }
        private class HeapNode<TObj>
        {
            public HeapNode(float k, TObj o)
            {
                obj = o;
                heapValue = k;
                index = -42;
            }

            public bool IsInHeap() { return index != -42; }
            public void RemoveFromHeap() { index = -42; }

            public TObj obj;
            public float heapValue;
            public int index;
        }

        private class MinHeap<TObj>
        {
            List<HeapNode<TObj>> _buffer;

            public MinHeap(int capacity = 8)
            {
                _buffer = new List<HeapNode<TObj>>(capacity);
            }

            static int _parent(int i) { return (i - 1) >> 1; }
            static int _left(int i) { return i + i + 1; }
            static int _right(int i) { return i + i + 2; }

            void _place(HeapNode<TObj> node, int i)
            {
                _buffer[i] = node;
                node.index = i;
            }

            void _swap(int i, int j)
            {
                HeapNode<TObj> tmp = _buffer[i];

                _place(_buffer[j], i);
                _place(tmp, j);
            }

            void _upheap(int i)
            {
                HeapNode<TObj> moving = _buffer[i];
                int index = i;
                int p = _parent(i);

                while (index > 0 && moving.heapValue < _buffer[p].heapValue)
                {
                    _place(_buffer[p], index);
                    index = p;
                    p = _parent(p);
                }

                if (index != i) _place(moving, index);
            }

            void _downheap(int i)
            {
                int length = _buffer.Count;
                if (!(i < length)) return;

                HeapNode<TObj> moving = _buffer[i];
                int index = i;
                int l = _left(i);
                int r = _right(i);
                int largest;

                while (l < length)
                {
                    if (r < length && _buffer[l].heapValue > _buffer[r].heapValue) largest = r;
                    else largest = l;

                    if (moving.heapValue > _buffer[largest].heapValue)
                    {
                        _place(_buffer[largest], index);
                        index = largest;
                        l = _left(index);
                        r = l + 1; //right(index);
                    }
                    else
                    {
                        break;
                    }
                }

                if (index != i) _place(moving, index);
            }

            public int Size()
            {
                return _buffer.Count;
            }

            public HeapNode<TObj> Insert(HeapNode<TObj> node)
            {
                int i = _buffer.Count;
                node.index = i;
                _buffer.Add(node);
                _upheap(i);
                return node;
            }

            public void Update(HeapNode<TObj> node, float v)
            {
                node.heapValue = v;
                //  	if(!node.IsInHeap()) {
                //Debug.LogError("Updateing node which is not in heap!");
                //		return;
                //	}

                int i = node.index;

                if (i > 0 && v < _buffer[_parent(i)].heapValue) _upheap(i);
                else _downheap(i);
            }

            public HeapNode<TObj> Extract()
            {
                int p = _buffer.Count - 1;
                if (p < 0) return null;

                _swap(0, p);

                HeapNode<TObj> dead = _buffer[p];
                _buffer.RemoveAt(p);
                dead.RemoveFromHeap();

                _downheap(0);
                return dead;
            }

            public HeapNode<TObj> Remove(HeapNode<TObj> node)
            {
                if (!node.IsInHeap())
                {
                    //Debug.LogError("Removing node which is not in heap!");
                    return null;
                }

                int i = node.index;
                int p = _buffer.Count - 1;
                _swap(i, p);
                _buffer.RemoveAt(p);
                node.RemoveFromHeap();

                if (_buffer[i].heapValue > node.heapValue) _downheap(i);
                else _upheap(i);

                return node;
            }
        }



        private static void BuildLodMesh(Mesh target, int faceCount, bool highQuality = true)
        {
            MeshEdges kmesh = new MeshEdges();
            Simplify sim = new Simplify();
            SimplifyParameters simpars = new SimplifyParameters();

            UnityMeshToMeshEdges(target, kmesh);
            simpars.targetFaceCount = faceCount;
            simpars.recalculateVertexPositions = highQuality;
            simpars.checkTopology = !highQuality;
            simpars.maxEdgesPerVertex = highQuality ? 18 : 0;
            if (highQuality == false)
            {
                simpars.preventNonManifoldEdges = false;
                simpars.boneWeightProtection = 0.0f;
                simpars.vertexColorProtection = 0.0f;
            }
            sim.Execute(ref kmesh, simpars);
            MeshEdgesToUnityMesh(kmesh, target);
        }

        private static void UnityMeshToMeshEdges(UnityEngine.Mesh unityMesh, MeshEdges meshEdges, float tolerance = 0.0f)
        {
            Vector3[] verts = unityMesh.vertices;
            Vector3[] normals = unityMesh.normals;
            Color[] vertColors = unityMesh.colors;
            BoneWeight[] boneWeights = unityMesh.boneWeights;
            Vector2[] uv1 = unityMesh.uv;
            Vector2[] uv2 = unityMesh.uv2;

            meshEdges.Clear();
            meshEdges.numMaterials = unityMesh.subMeshCount;
            meshEdges.equalityTolerance = tolerance;

            int numVerts = verts.Length;
            meshEdges.hasVertexColors = (vertColors.Length == numVerts);
            meshEdges.bindposes = unityMesh.bindposes;
            meshEdges.hasBoneWeights = (meshEdges.bindposes != null && boneWeights.Length == numVerts);
            meshEdges.hasUV1 = (uv1.Length == numVerts);
            meshEdges.hasUV2 = (uv2.Length == numVerts);

            int i;
            for (i = 0; i < numVerts; ++i)
            {
                meshEdges.AddVertex(verts[i]);
            }
            if (meshEdges.hasVertexColors)
            {
                for (i = 0; i < numVerts; ++i)
                {
                    meshEdges.vertices[i].color = vertColors[i];
                }
            }
            if (meshEdges.hasBoneWeights)
            {
                for (i = 0; i < numVerts; ++i)
                {
                    meshEdges.vertices[i].boneWeight = boneWeights[i];
                }
            }

            bool quad = true;
            for (int m = 0; m < meshEdges.numMaterials; ++m)
            {
                if (unityMesh.GetTopology(m) != UnityEngine.MeshTopology.Quads)
                {
                    quad = false;
                    break;
                }
            }

            int v0, v1, v2, v3;
            for (int m = 0; m < meshEdges.numMaterials; ++m)
            {
                if (quad)
                {
                    int[] indices = unityMesh.GetIndices(m);
                    int num = indices.Length;
                    for (i = 0; i < num;)
                    {
                        v0 = indices[i++]; v1 = indices[i++]; v2 = indices[i++]; v3 = indices[i++];
                        Face f = new Face(v0, v1, v2, v3);
                        meshEdges.AddFace(f);
                        f.vertexNormal[0] = normals[v0]; f.vertexNormal[1] = normals[v1]; f.vertexNormal[2] = normals[v2]; f.vertexNormal[3] = normals[v3];
                        if (meshEdges.hasUV1)
                        {
                            f.uv1[0] = uv1[v0]; f.uv1[1] = uv1[v1]; f.uv1[2] = uv1[v2]; f.uv1[3] = uv1[v3];
                        }
                        if (meshEdges.hasUV2)
                        {
                            f.uv2[0] = uv2[v0]; f.uv2[1] = uv2[v1]; f.uv2[2] = uv2[v2]; f.uv2[3] = uv2[v3];
                        }
                        f.material = m;
                    }
                }
                else
                {
                    int[] tris = unityMesh.GetTriangles(m);
                    int num = tris.Length;
                    for (i = 0; i < num;)
                    {
                        v0 = tris[i++]; v1 = tris[i++]; v2 = tris[i++];
                        Face f = new Face(v0, v1, v2);
                        meshEdges.AddFace(f);
                        f.vertexNormal[0] = normals[v0]; f.vertexNormal[1] = normals[v1]; f.vertexNormal[2] = normals[v2];
                        if (meshEdges.hasUV1)
                        {
                            f.uv1[0] = uv1[v0]; f.uv1[1] = uv1[v1]; f.uv1[2] = uv1[v2];
                        }
                        if (meshEdges.hasUV2)
                        {
                            f.uv2[0] = uv2[v0]; f.uv2[1] = uv2[v1]; f.uv2[2] = uv2[v2];
                        }
                        f.material = m;
                    }
                }
            }

            RemoveDoubleVertices(meshEdges);
            meshEdges.GenerateEdgeList();
            meshEdges.CalculateEdgeLinkedFaces();
            meshEdges.topology = quad ? MeshTopology.Quads : MeshTopology.Triangles;
            CreaseDetect.MarkCreasesFromFaceNormals(meshEdges);
        }
        private static void Triangulate(BaseMesh mesh)
        {
            if (mesh.topology == MeshTopology.Triangles) return;

            int faceIndex, cornerIndex;

            int numFaces = mesh.faceCount();
            Vector3[] coords = new Vector3[4];
            for (faceIndex = 0; faceIndex < numFaces; ++faceIndex)
            {
                Face f = mesh.faces[faceIndex];
                if (f.valid && f.cornerCount == 4)
                {
                    Face fnew = new Face(3);
                    // copy face parameters
                    fnew.material = f.material;
                    for (cornerIndex = 0; cornerIndex < 4; ++cornerIndex)
                    {
                        coords[cornerIndex] = mesh.vertices[f.v[cornerIndex]].coords;
                    }

                    Vector3 diag02 = coords[0] - coords[2];
                    Vector3 diag13 = coords[1] - coords[3];
                    if (diag02.sqrMagnitude < diag13.sqrMagnitude)
                    {
                        // Create triangles 201 and 023 .. use diag as first edge as a hint
                        fnew.CopyVertexInfoFromFace(f, 0, 0);
                        fnew.CopyVertexInfoFromFace(f, 2, 1);
                        fnew.CopyVertexInfoFromFace(f, 3, 2);
                    }
                    else
                    {
                        // Create triangles 312 and 013 .. use diag as first edge as a hint
                        fnew.CopyVertexInfoFromFace(f, 3, 0);
                        fnew.CopyVertexInfoFromFace(f, 1, 1);
                        fnew.CopyVertexInfoFromFace(f, 2, 2);
                        f.CopyVertexInfoFromFace(f, 3, 2);
                    }
                    mesh.AddFace(fnew);

                    f.cornerCount = 3;
                }
            }

            int numVerts = mesh.vertCount();
            for (int vertIndex = 0; vertIndex < numVerts; ++vertIndex)
            {
                mesh.vertices[vertIndex].linkedFaces = new IndexList(18);
            }
            numFaces = mesh.faceCount();

            for (faceIndex = 0; faceIndex < numFaces; ++faceIndex)
            {
                Face f = mesh.faces[faceIndex];
                for (cornerIndex = 0; cornerIndex < f.cornerCount; ++cornerIndex)
                {
                    mesh.vertices[f.v[cornerIndex]].linkedFaces.Add(faceIndex);
                }
            }
            mesh.topology = MeshTopology.Triangles;
        }
        private static void RemoveDoubleVertices(BaseMesh mesh)
        {
            int numVerts = mesh.vertCount();

            List<int> vertexOrder = new List<int>(numVerts);
            for (int i = 0; i < numVerts; ++i) vertexOrder.Add(i);

            vertexOrder.Sort(delegate (int a, int b) {
                return mesh.CompareVertices(a, b, mesh.equalityTolerance);
            });

            int[] uniqueVertex = new int[numVerts];
            int unique = vertexOrder[0];
            uniqueVertex[unique] = unique;
            for (int i = 1; i < numVerts; ++i)
            {
                int vertexIndex = vertexOrder[i];
                if (mesh.CompareVertices(unique, vertexIndex, mesh.equalityTolerance) != 0)
                {
                    unique = vertexIndex;
                }
                uniqueVertex[vertexIndex] = unique;
            }
            // TODO: maybe use the center of all the almost equal vertices .. probably does not matter
            for (int i = 0; i < numVerts; ++i)
            {
                if (i != uniqueVertex[i])
                {
                    mesh.ReplaceVertex(i, uniqueVertex[i]);
                }
            }
            /* int num = */
            mesh.InvalidateUnconnectedVertices();
            //Debug.Log("RemoveDoubleVertices Invalidated " + num + " unconnected vertices.");
            /*num = */
            mesh.InvalidateDegenerateFaces();
            // Debug.Log("RemoveDoubleVertices Invalidated " + num + " degenerate faces.");
        }


        private static void MeshEdgesToUnityMesh(MeshEdges meshEdges, UnityEngine.Mesh unityMesh)
        {
            meshEdges.InvalidateDegenerateFaces();
            //	Ops.TriangulateWithEdges(meshEdges);
            int numFaces = meshEdges.faceCount();

            List<UnityVertex> exVerts = new List<UnityVertex>(numFaces * 3);

            List<Vector3> verts = new List<Vector3>();
            List<int>[] indices = new List<int>[meshEdges.numMaterials];
            List<int> vertexTable = new List<int>();
            // Create a list of all vertices based on face corners (= lots of duplicates)
            for (int material = 0; material < meshEdges.numMaterials; ++material)
            {
                indices[material] = new List<int>();
                for (int faceIndex = 0; faceIndex < numFaces; ++faceIndex)
                {
                    Face f = meshEdges.faces[faceIndex];
                    if (f.valid && f.material == material)
                    {
                        int cornerCount = f.cornerCount;
                        for (int cornerIndex = 0; cornerIndex < cornerCount; ++cornerIndex)
                        {
                            UnityVertex ev = new UnityVertex();
                            ev.vertIndex = f.v[cornerIndex];
                            ev.cornerCount = cornerCount;
                            ev.material = material;
                            ev.coords = meshEdges.vertices[f.v[cornerIndex]].coords;
                            ev.normal = f.vertexNormal[cornerIndex];
                            ev.uv1 = f.uv1[cornerIndex];
                            ev.uv2 = f.uv2[cornerIndex];

                            vertexTable.Add(exVerts.Count);
                            exVerts.Add(ev);
                        }
                    }
                }
            }

            vertexTable.Sort(delegate (int a, int b) {
                return UnityVertex.CompareEV(exVerts[a], exVerts[b]);
            });

            // Create index list to collapse list to the unique verts added to the final mesh
            // compile unique mesh arrays
            List<Vector3> normals = new List<Vector3>();
            List<Vector2> uv1 = new List<Vector2>();
            List<Vector2> uv2 = new List<Vector2>();
            List<Color> vertColors = new List<Color>();
            List<BoneWeight> boneWeights = new List<BoneWeight>();
            int[] uniqueTable = new int[exVerts.Count];
            int numUnique = 0;
            for (int i = 0; i < exVerts.Count; ++i)
            {
                int exIndex = vertexTable[i];
                if (i == 0 || UnityVertex.CompareEV(exVerts[vertexTable[i - 1]], exVerts[exIndex]) != 0)
                {
                    verts.Add(exVerts[exIndex].coords);
                    normals.Add(exVerts[exIndex].normal);
                    uv1.Add(exVerts[exIndex].uv1);
                    uv2.Add(exVerts[exIndex].uv2);

                    Vertex v = meshEdges.vertices[exVerts[exIndex].vertIndex];
                    vertColors.Add(v.color);
                    boneWeights.Add(v.boneWeight);
                    numUnique++;
                }
                uniqueTable[exIndex] = numUnique - 1;
            }

            {
                // Mixed tris/quads need to split quads
                int cornerCount, mat;
                int i0, i1, i2, i3;
                for (i0 = 0; i0 < exVerts.Count;)
                {
                    i1 = i0 + 1;
                    i2 = i0 + 2;
                    cornerCount = exVerts[i0].cornerCount;
                    mat = exVerts[i0].material;
                    if (cornerCount == 3)
                    {
                        indices[mat].Add(uniqueTable[i0]);
                        indices[mat].Add(uniqueTable[i1]);
                        indices[mat].Add(uniqueTable[i2]);
                        i0 += 3;
                    }
                    else
                    { // Quad!
                        i3 = i0 + 3;
                        Vector3 diag02 = exVerts[i0].coords - exVerts[i2].coords;
                        Vector3 diag13 = exVerts[i1].coords - exVerts[i3].coords;

                        if (diag02.sqrMagnitude > diag13.sqrMagnitude)
                        {
                            // If 0-2 if shorter than 1-3
                            indices[mat].Add(uniqueTable[i0]);
                            indices[mat].Add(uniqueTable[i1]);
                            indices[mat].Add(uniqueTable[i2]);

                            indices[mat].Add(uniqueTable[i0]);
                            indices[mat].Add(uniqueTable[i2]);
                            indices[mat].Add(uniqueTable[i3]);
                        }
                        else
                        {
                            indices[mat].Add(uniqueTable[i0]);
                            indices[mat].Add(uniqueTable[i1]);
                            indices[mat].Add(uniqueTable[i3]);

                            indices[mat].Add(uniqueTable[i1]);
                            indices[mat].Add(uniqueTable[i2]);
                            indices[mat].Add(uniqueTable[i3]);
                        }
                        i0 += 4;
                    }
                }
            }

            unityMesh.Clear(false);
            unityMesh.name = "Mesh";

            if (verts.Count >= 65536 || exVerts.Count >= 65536 * 3)
            {
                Debug.Log("Cannot create Unity mesh from LodlMesh.MeshEdges. " +
                    "The mesh is too large (>65k). " +
                    "Vertices: " + verts.Count + " Triangles: " + exVerts.Count / 3);
                return;
            }

            unityMesh.subMeshCount = meshEdges.numMaterials;
            unityMesh.vertices = verts.ToArray();
            unityMesh.normals = normals.ToArray();
            if (meshEdges.hasVertexColors)
            {
                unityMesh.colors = vertColors.ToArray();
            }
            if (meshEdges.hasUV1)
            {
                unityMesh.uv = uv1.ToArray();
            }
            if (meshEdges.hasUV2)
            {
                unityMesh.uv2 = uv2.ToArray();
            }
            if (meshEdges.hasBoneWeights)
            {
                unityMesh.bindposes = meshEdges.bindposes;
                unityMesh.boneWeights = boneWeights.ToArray();
            }
#if false
			if (meshEdges.topology == MeshTopology.Quads) {
				for (int mat = 0; mat < meshEdges.numMaterials; ++mat) {
					unityMesh.SetIndices(indices[mat].ToArray(), UnityEngine.MeshTopology.Quads, mat);
				}
			} else
#endif
            {
                for (int mat = 0; mat < meshEdges.numMaterials; ++mat)
                {
                    unityMesh.SetTriangles(indices[mat].ToArray(), mat);
                }
            }
            if (meshEdges.hasUV1 && meshEdges.calculateTangents)
            {
                _calculateMeshTangents(unityMesh);
            }
        }

        private static void _calculateMeshTangents(UnityEngine.Mesh mesh)
        {
            int[] triangles = mesh.triangles;
            Vector3[] vertices = mesh.vertices;
            Vector2[] uv = mesh.uv;
            Vector3[] normals = mesh.normals;

            //variable definitions
            int triangleCount = triangles.Length;
            int vertexCount = vertices.Length;

            Vector3[] tan1 = new Vector3[vertexCount];
            Vector3[] tan2 = new Vector3[vertexCount];
            Vector4[] tangents = new Vector4[vertexCount];
            Vector3 sdir, tdir, e1, e2;
            Vector2 s, t;
            float div, r;
            int a, i1, i2, i3;
            for (a = 0; a < triangleCount; a += 3)
            {
                i1 = triangles[a];
                i2 = triangles[a + 1];
                i3 = triangles[a + 2];

                e1 = vertices[i2] - vertices[i1];
                e2 = vertices[i3] - vertices[i1];

                s = uv[i2] - uv[i1]; // UV side vectors
                t = uv[i3] - uv[i1];

                div = (s.x * t.y - s.y * t.x); // Cross product = 2xarea of uv triangle
                r = (div != 0.0f) ? 1.0f / div : 0.0f;

                t *= r;
                s *= r;

                sdir = t.y * e1 - s.y * e2;
                tdir = s.x * e2 - t.x * e1;

                tan1[i1] += sdir;
                tan1[i2] += sdir;
                tan1[i3] += sdir;

                tan2[i1] += tdir;
                tan2[i2] += tdir;
                tan2[i3] += tdir;
            }
            Vector3 n, tan;
            //float val1, val2;
            //int axis;
            for (a = 0; a < vertexCount; ++a)
            {
                n = normals[a];
                tan = tan1[a];

                Vector3.OrthoNormalize(ref n, ref tan);
                tangents[a] = tan;



                tangents[a].w = (Vector3.Dot(Vector3.Cross(n, tan), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
            }

            mesh.tangents = tangents;
        }

        private class UnityVertex
        {
            public int vertIndex;
            public int material;
            public int cornerCount;
            public Vector3 coords;
            public Vector3 normal;
            public Vector2 uv1;
            public Vector2 uv2;
            public static int CompareEV(UnityVertex a, UnityVertex b)
            {
                int res = Vector3Compare(a.coords, b.coords);
                if (res != 0) return res;
                res = Vector3Compare(a.normal, b.normal);
                if (res != 0) return res;
                res = Vector2Compare(a.uv1, b.uv1);
                if (res != 0) return res;
                res = Vector2Compare(a.uv2, b.uv2);
                return res;
            }
            private static int Vector3Compare(Vector3 a, Vector3 b)
            {
                if (a.x < b.x) return -1;
                if (a.x > b.x) return 1;
                if (a.y < b.y) return -1;
                if (a.y > b.y) return 1;
                if (a.z < b.z) return -1;
                if (a.z > b.z) return 1;
                return 0;
            }
            private static int Vector2Compare(Vector2 a, Vector2 b)
            {
                if (a.x < b.x) return -1;
                if (a.x > b.x) return 1;
                if (a.y < b.y) return -1;
                if (a.y > b.y) return 1;
                return 0;
            }
        }

    }

    
}

